diff --git a/framework/doc/content/bib/moose.bib b/framework/doc/content/bib/moose.bib index 40a28ddf9884..40183a629b63 100644 --- a/framework/doc/content/bib/moose.bib +++ b/framework/doc/content/bib/moose.bib @@ -609,3 +609,15 @@ @article{liu2015finite year={2015}, publisher={Taylor \& Francis} } + +@article{rao2018stopping, + title = {A stopping criterion for the iterative solution of partial differential equations}, + journal = {Journal of Computational Physics}, + volume = {352}, + pages = {265-284}, + year = {2018}, + issn = {0021-9991}, + doi = {https://doi.org/10.1016/j.jcp.2017.09.033}, + url = {https://www.sciencedirect.com/science/article/pii/S0021999117306939}, + author = {Kaustubh Rao and Paul Malan and J. Blair Perot} +} diff --git a/framework/doc/content/source/actions/AddConvergenceAction.md b/framework/doc/content/source/actions/AddConvergenceAction.md new file mode 100644 index 000000000000..31223a356f06 --- /dev/null +++ b/framework/doc/content/source/actions/AddConvergenceAction.md @@ -0,0 +1,5 @@ +# AddConvergenceAction + +Adds a [Convergence](syntax/Convergence/index.md) object to the `Problem`. + +!syntax parameters /Convergence/AddConvergenceAction diff --git a/framework/doc/content/source/actions/AddDefaultConvergenceAction.md b/framework/doc/content/source/actions/AddDefaultConvergenceAction.md new file mode 100644 index 000000000000..b396b3b0c4c4 --- /dev/null +++ b/framework/doc/content/source/actions/AddDefaultConvergenceAction.md @@ -0,0 +1,4 @@ +# AddDefaultConvergenceAction + +This action adds a default [Convergence](syntax/Convergence/index.md) object if requested +by the `Problem`. diff --git a/framework/doc/content/source/convergence/DefaultNonlinearConvergence.md b/framework/doc/content/source/convergence/DefaultNonlinearConvergence.md new file mode 100644 index 000000000000..650c6de66a1d --- /dev/null +++ b/framework/doc/content/source/convergence/DefaultNonlinearConvergence.md @@ -0,0 +1,74 @@ +# DefaultNonlinearConvergence + +This [Convergence](Convergence/index.md) is the default convergence for +[/FEProblem.md], using a combination of criteria to determine convergence. + +!alert warning title=Shared executioner parameters +This object shares several parameters with the executioner. If one of these +parameters is set by the user in the executioner and not in the convergence +object, then the value is taken from the executioner, rather than the default +value. If a parameter is set by the user in both the executioner and the convergence +object, an error is thrown. + +Consider the system of algebraic equations: + +!equation +\mathbf{r}(\mathbf{u}) = \mathbf{0} \,. + +This class reports convergence of the solution to this system if ++any+ of the following conditions are true: + +!equation +\|\mathbf{r}\|_2 < \tau_\text{abs} \,, + +!equation +\frac{\|\mathbf{r}\|_2}{\|\mathbf{r}_0\|_2} < \tau_\text{rel} \,, + +!equation +\frac{\|\mathbf{\delta u}\|_2}{\|\mathbf{u}\|_2} < \tau_{\delta u,\text{rel}} \,. + +This class reports divergence if +any+ of the following conditions are true: + +!equation +\|\mathbf{r}\|_2 = \text{NaN} \,, + +!equation +n_\text{evals} \geq n_\text{evals,max} \,, + +!equation +\|\mathbf{r}\|_2 > \tau_\text{div,abs} \,, + +!equation +\frac{\|\mathbf{r}\|_2}{\|\mathbf{r}_0\|_2} > \tau_\text{div,rel} \,, + +!equation +n_\text{ping} > n_\text{ping,max} \,, + +where + +- $\|\cdot\|_2$ is the discrete $L_2$ norm, +- $\mathbf{r}_0$ is the initial (guess) residual vector, +- $\mathbf{\delta u} = \mathbf{u}^{(\ell)} - \mathbf{u}^{(\ell-1)}$ is the solution step vector, +- "NaN" is a not-a-number value, +- $\tau_\text{abs}$ is the absolute residual tolerance, specified with the + [!param](/Convergence/DefaultNonlinearConvergence/nl_abs_tol) parameter. +- $\tau_\text{rel}$ is the relative residual tolerance, provided by + [!param](/Convergence/DefaultNonlinearConvergence/nl_rel_tol). +- $\tau_{\delta u,\text{abs}}$ is the relative step tolerance., provided by + [!param](/Convergence/DefaultNonlinearConvergence/nl_rel_step_tol). +- $\tau_\text{div,abs}$ is the absolute residual divergence tolerance, provided by + [!param](/Convergence/DefaultNonlinearConvergence/nl_abs_div_tol). +- $\tau_\text{div,rel}$ is the relative residual divergence tolerance, provided by + [!param](/Convergence/DefaultNonlinearConvergence/nl_div_tol). +- $n_\text{evals}$ is the number of residual evaluations. +- $n_\text{evals,max}$ is the maximum number of residual evaluations, provided by + [!param](/Convergence/DefaultNonlinearConvergence/nl_max_funcs). +- $n_\text{ping}$ is the number of ping-pong iterations (consecutive iterations in which the residual grows then reduces on every other iteration). +- $n_\text{ping,max}$ is the maximum number of ping-pong iterations, provided by + [!param](/Convergence/DefaultNonlinearConvergence/n_max_nonlinear_pingpong). + +!syntax parameters /Convergence/DefaultNonlinearConvergence + +!syntax inputs /Convergence/DefaultNonlinearConvergence + +!syntax children /Convergence/DefaultNonlinearConvergence diff --git a/framework/doc/content/source/convergence/IterationCountConvergence.md b/framework/doc/content/source/convergence/IterationCountConvergence.md new file mode 100644 index 000000000000..93862cd88065 --- /dev/null +++ b/framework/doc/content/source/convergence/IterationCountConvergence.md @@ -0,0 +1,24 @@ +# IterationCountConvergence + +This [Convergence](Convergence/index.md) specifies: + +- $\ell_\text{max}$, the maximum number of iterations, + via [!param](/Convergence/IterationCountConvergence/max_iterations), and +- $\ell_\text{min}$, the minimum number of iterations, + via [!param](/Convergence/IterationCountConvergence/min_iterations). + +If [!param](/Convergence/IterationCountConvergence/converge_at_max_iterations) +is set to `true`, then the solve will converge when $\ell = \ell_\text{max}$ +instead of diverge. + +## Design + +Other `Convergence` objects may inherit from this class and override +`checkConvergenceInner(iter)` instead of the usual `checkConvergence(iter)`, +to inherit the iteration bounds. An example is [/PostprocessorConvergence.md]. + +!syntax parameters /Convergence/IterationCountConvergence + +!syntax inputs /Convergence/IterationCountConvergence + +!syntax children /Convergence/IterationCountConvergence diff --git a/framework/doc/content/source/convergence/PostprocessorConvergence.md b/framework/doc/content/source/convergence/PostprocessorConvergence.md new file mode 100644 index 000000000000..3eee64f6ee44 --- /dev/null +++ b/framework/doc/content/source/convergence/PostprocessorConvergence.md @@ -0,0 +1,19 @@ +# PostprocessorConvergence + +This [Convergence](Convergence/index.md) derives from [/IterationCountConvergence.md] +and compares a [Postprocessor](Postprocessors/index.md) value $y$ to a tolerance $\tau$: + +!equation +|y| \leq \tau \,. + +For this to work as expected, the `execute_on` parameter of the post-processor +must include values that trigger execution before the desired check. For example, for assessing convergence of the nonlinear solve, the value `NONLINEAR_CONVERGENCE` should be used. + +Typically the post-processor used should attempt to approximate the error in a system, +such as [/AverageVariableChange.md]. + +!syntax parameters /Convergence/PostprocessorConvergence + +!syntax inputs /Convergence/PostprocessorConvergence + +!syntax children /Convergence/PostprocessorConvergence diff --git a/framework/doc/content/source/convergence/ReferenceResidualConvergence.md b/framework/doc/content/source/convergence/ReferenceResidualConvergence.md new file mode 100644 index 000000000000..c8c29ba0ed87 --- /dev/null +++ b/framework/doc/content/source/convergence/ReferenceResidualConvergence.md @@ -0,0 +1,11 @@ +# ReferenceResidualConvergence + +This [Convergence](Convergence/index.md) is a [/DefaultNonlinearConvergence.md] with a +customized reference residual for its relative convergence checks. See +[ReferenceResidualProblem.md] for more information. + +!syntax parameters /Convergence/ReferenceResidualConvergence + +!syntax inputs /Convergence/ReferenceResidualConvergence + +!syntax children /Convergence/ReferenceResidualConvergence diff --git a/framework/doc/content/source/postprocessors/PseudoTimestep.md b/framework/doc/content/source/postprocessors/PseudoTimestep.md index 3f8c2603cb84..23a344d2663f 100644 --- a/framework/doc/content/source/postprocessors/PseudoTimestep.md +++ b/framework/doc/content/source/postprocessors/PseudoTimestep.md @@ -16,8 +16,8 @@ This object computes a timestep to accelerate the convergence to steady-state us The change in timestep is determined by the steady-state residual behavior from one iteration to another, i.e., small changes in residual indicate larger timesteps are allowed. In contrast, significant changes in the residual indicate a timestep decrease is necessary. Following [!citep](bucker2009cfl), we recognize and implement three methods. -The user must make a method choice, between `SER`, `EXP` and `RDM`, which are implemented as listed below. -All methods require a parameter [!param](/Postprocessors/PseudoTimestep/alpha), which controls how sensitive the timestep should be with respect to residual changes, and [!param](/Postprocessors/PseudoTimestep/initial_dt) to provide a first timestep length. +The user must make a method choice, between `SER`, `EXP` and `RDM`, which are implemented as listed below. +All methods require a parameter [!param](/Postprocessors/PseudoTimestep/alpha), which controls how sensitive the timestep should be with respect to residual changes, and [!param](/Postprocessors/PseudoTimestep/initial_dt) to provide a first timestep length. If nothing is known about the problem we recommend `initial_dt = 1` and `alpha = 2`, keeping in mind that a high [!param](/Postprocessors/PseudoTimestep/alpha) corresponds to a higher sensitivity to residual changes. More specific choices for fluid dynamics problems are available in [!citep](bucker2009cfl) or [!citep](ceze2013pseudo). The parameter [!param](/Postprocessors/PseudoTimestep/alpha) is always larger than 0, noting that for some versions of Pseudo Timestep Continuation methods it can be lower than 1. We refer the user to the literature, or to perform a preliminary study for their specific problem. Methods supported include: @@ -46,12 +46,12 @@ Methods supported include: where $\alpha$ is a user chosen parameter, $k$ is the current iteration step. -As noted also in [!citep](bucker2009cfl) the EXP method has an infinite growth, so for this method a [!param](/Postprocessors/PseudoTimestep/max_dt) parameter may be recommended. If no [!param](/Postprocessors/PseudoTimestep/max_dt) is provided by the user then infinite growth of the timestep is not bounded and the user will be informed by a message at the console. Ideally this method is used in conjunction with a steady state detection, i.e. setting [!param](/Executioner/Transient/steady_state_detection) to `true` and a [!param](/Executioner/Transient/steady_state_tolerance). +As noted also in [!citep](bucker2009cfl) the EXP method has an infinite growth, so for this method a [!param](/Postprocessors/PseudoTimestep/max_dt) parameter may be recommended. If no [!param](/Postprocessors/PseudoTimestep/max_dt) is provided by the user then infinite growth of the timestep is not bounded and the user will be informed by a message at the console. Ideally this method is used in conjunction with a steady state detection, i.e. setting [!param](/Executioner/Transient/steady_state_detection) to `true` and a [!param](/Executioner/Transient/steady_state_tolerance). ## Example Input File Syntax -!listing test/tests/postprocessors/pseudotimestep/fv_burgers_pseudo.i - block=Postprocessors +!listing test/tests/postprocessors/pseudotimestep/fv_burgers_pseudo.i + block=Postprocessors !syntax parameters /Postprocessors/PseudoTimestep @@ -59,4 +59,4 @@ As noted also in [!citep](bucker2009cfl) the EXP method has an infinite growth, !syntax children /Postprocessors/PseudoTimestep -!bibtex bibliography \ No newline at end of file +!bibtex bibliography diff --git a/framework/doc/content/syntax/Convergence/index.md b/framework/doc/content/syntax/Convergence/index.md new file mode 100644 index 000000000000..9572c781c03b --- /dev/null +++ b/framework/doc/content/syntax/Convergence/index.md @@ -0,0 +1,125 @@ +# Convergence System + +The Convergence system allows users to customize the stopping criteria for the +iteration in various solves: + +- Nonlinear system solves +- Linear system solves (not yet implemented) +- Steady-state detection in [Transient.md] (not yet implemented) +- Fixed point solves with [MultiApps](syntax/MultiApps/index.md) (not yet implemented) +- Fixed point solves with multiple systems (not yet implemented) + +Instead of supplying convergence-related parameters directly to the executioner, +the user creates `Convergence` objects whose names are then supplied to the +executioner, e.g., + +``` +[Convergence] + [my_convergence1] + type = MyCustomConvergenceClass + # some convergence parameters, like tolerances + [] +[] + +[Executioner] + type = Steady + nonlinear_convergence = my_convergence1 +[] +``` + +Currently only the nonlinear solve convergence is supported, but others are planned +for the near future. If the `nonlinear_convergence` parameter is not specified, +then the default `Convergence` associated with the problem is created internally. + +!syntax list /Convergence objects=True actions=False subsystems=False + +!syntax list /Convergence objects=False actions=False subsystems=True + +!syntax list /Convergence objects=False actions=True subsystems=False + +## Convergence Criteria Design Considerations + +Here we provide some considerations to make in designing convergence criteria +and choosing appropriate parameter values. +Consider a system of algebraic system of equations + +!equation +\mathbf{r}(\mathbf{u}) = \mathbf{0} \,, + +where $\mathbf{u}$ is the unknown solution vector, and $\mathbf{r}$ is the residual +function. To solve this system using an iterative method, we must decide on +criteria to stop the iteration. +In general, iteration for a solve should halt when the approximate solution $\tilde{\mathbf{u}}$ +has reached a satisfactory level of error $\mathbf{e} \equiv \tilde{\mathbf{u}} - \mathbf{u}$, +using a condition such as + +!equation id=error_criteria +\|\mathbf{e}\| \leq \tau_u \,, + +where $\|\cdot\|$ denotes some norm, and $\tau_u$ denotes some tolerance. +Unfortunately, since we do not know $\mathbf{u}$, the error $\mathbf{e}$ is +also unknown and thus may not be computed directly. Thus some approximation of +the condition [!eqref](error_criteria) must be made. This may entail some +approximation of the error $\mathbf{e}$ or some criteria which implies the +desired criteria. For example, a very common approach is to use a residual +criteria such as + +!equation +\|\mathbf{r}\| \leq \tau_{r,\text{abs}} \,. + +While it is true that $\|\mathbf{r}\| = 0$ implies $\|\mathbf{e}\| = 0$, a +zero-tolerance is impractical, and the translation between the tolerance +$\tau_u$ to the tolerance is $\tau_r$ is difficult. The "acceptable" absolute +residual tolerance is tricky to determine and is highly dependent on the +equations being solved. To attempt to circumvent this issue, relative residual +criteria have been used, dividing the residual norm by another value in an +attempt to normalize it. A common approach that has been used is to use the +initial residual vector $\mathbf{r}_0$ to normalize: + +!equation +\frac{\|\mathbf{r}\|}{\|\mathbf{r}_0\|} \leq \tau_{r,\text{rel}} \,, + +where $\tau_{r,\text{rel}}$ is the relative residual tolerance. The disadvantage +with this particular choice is that this is highly dependent on how good the +initial guess is: if the initial guess is very good, it will be nearly impossible +to converge to the tolerance, and if the initial guess is very bad, it will be +too easy to converge to the tolerance, resulting in an erroneous solution. + +Some other considerations are the following: + +- Consider round-off error: if error ever reaches values around round-off error, + the solve should definitely be considered converged, as iterating further + provides no benefit. +- Consider the other sources of error in the model that produced the system of + algebraic equations that you're solving. For example, if solving a system of + partial differential equations, consider the model error and the discretization + error; it is not beneficial to require algebraic error less than the other + sources of error. +- Since each convergence criteria typically has some weak point where they break + down, it is usually advisable to use a combination of criteria. + +For more information on convergence criteria, see [!cite](rao2018stopping) for +example. + +!alert tip title=Create your own convergence action +The `Convergence` system provides a lot of flexibility by providing several +pieces that can be combined together to create a desired set of convergence +criteria. Since this may involve a large number of objects (including objects +from other systems), it may be beneficial to create an [Action](/Action.md) +to create more compact and convenient syntax for your application. + +## Implementing a New Convergence Class + +`Convergence` objects are responsible for overriding the virtual method + +``` +MooseConvergenceStatus checkConvergence(unsigned int iter) +``` + +The returned type `MooseConvergenceStatus` is one of the following values: + +- `CONVERGED`: The system has converged. +- `DIVERGED`: The system has diverged. +- `ITERATING`: The system has neither converged nor diverged and thus will + continue to iterate. + diff --git a/framework/include/actions/AddConvergenceAction.h b/framework/include/actions/AddConvergenceAction.h new file mode 100644 index 000000000000..9f690edb5d34 --- /dev/null +++ b/framework/include/actions/AddConvergenceAction.h @@ -0,0 +1,25 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "MooseObjectAction.h" + +/** + * Add a Convergence object to the simulation. + */ +class AddConvergenceAction : public MooseObjectAction +{ +public: + static InputParameters validParams(); + + AddConvergenceAction(const InputParameters & params); + + virtual void act() override; +}; diff --git a/framework/include/actions/AddDefaultConvergenceAction.h b/framework/include/actions/AddDefaultConvergenceAction.h new file mode 100644 index 000000000000..2a3e3b8f6255 --- /dev/null +++ b/framework/include/actions/AddDefaultConvergenceAction.h @@ -0,0 +1,25 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "Action.h" + +/** + * Add a default Convergence object to the simulation. + */ +class AddDefaultConvergenceAction : public Action +{ +public: + static InputParameters validParams(); + + AddDefaultConvergenceAction(const InputParameters & params); + + virtual void act() override; +}; diff --git a/framework/include/convergence/Convergence.h b/framework/include/convergence/Convergence.h new file mode 100644 index 000000000000..d38186f584e5 --- /dev/null +++ b/framework/include/convergence/Convergence.h @@ -0,0 +1,52 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "MooseObject.h" +#include "SetupInterface.h" +#include "PostprocessorInterface.h" +#include "PerfGraphInterface.h" + +/** + * Base class for convergence criteria. + */ +class Convergence : public MooseObject, + public SetupInterface, + public PostprocessorInterface, + public PerfGraphInterface +{ +public: + static InputParameters validParams(); + + /** + * Status returned by calls to \c checkConvergence. + */ + enum class MooseConvergenceStatus + { + ITERATING = 0, + CONVERGED = 1, + DIVERGED = -1 + }; + + Convergence(const InputParameters & parameters); + + virtual void initialSetup() override{}; + + /** + * Returns convergence status. + * + * @param[in] iter Iteration index + */ + virtual MooseConvergenceStatus checkConvergence(unsigned int iter) = 0; + +protected: + /// Performance ID for \c checkConvergence + PerfID _perfid_check_convergence; +}; diff --git a/framework/include/convergence/DefaultNonlinearConvergence.h b/framework/include/convergence/DefaultNonlinearConvergence.h new file mode 100644 index 000000000000..1ee72636f767 --- /dev/null +++ b/framework/include/convergence/DefaultNonlinearConvergence.h @@ -0,0 +1,111 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "Convergence.h" +#include "MooseApp.h" +#include "Executioner.h" + +/** + * Default convergence criteria for FEProblem + */ +class DefaultNonlinearConvergence : public Convergence +{ +public: + static InputParameters validParams(); + + static InputParameters residualConvergenceParams(); + + DefaultNonlinearConvergence(const InputParameters & parameters); + + virtual void initialSetup() override; + + virtual MooseConvergenceStatus checkConvergence(unsigned int iter) override; + +protected: + /** + * Check the relative convergence of the nonlinear solution + * @param fnorm Norm of the residual vector + * @param ref_norm Norm to use for reference value + * @param rel_tol Relative tolerance + * @param abs_tol Absolute tolerance + * @return Bool signifying convergence + */ + virtual bool checkRelativeConvergence(const unsigned int it, + const Real fnorm, + const Real ref_norm, + const Real rel_tol, + const Real abs_tol, + std::ostringstream & oss); + + /** + * Performs setup necessary for each call to checkConvergence + */ + virtual void nonlinearConvergenceSetup() {} + + /** + * This method is to be used for parameters that are shared with the executioner. + * + * If the parameter is set by the user in the executioner, get that value; + * otherwise, get this object's value. + * If the parameter is set by the user in both this object and the executioner, + * add it to a list and report an error later. + */ + template + const T & getSharedExecutionerParam(const std::string & name); + + /** + * Throws an error if any of the parameters shared with the executioner have + * been set by the user in both places. + * + * This should be called after all calls to \c getSharedExecutionerParam. + * No error is thrown if the Convergence object was added as a default, since + * in that case, any parameters set by the user in the executioner will also + * be considered set by the user in the Convergence. + */ + void checkDuplicateSetSharedExecutionerParams() const; + +private: + /// True if this object was added as a default instead of by the user + const bool _added_as_default; + + /// List of shared executioner parameters that have been set by the user in both places + std::vector _duplicate_shared_executioner_params; + +protected: + FEProblemBase & _fe_problem; + /// Nonlinear absolute divergence tolerance + const Real _nl_abs_div_tol; + /// Nonlinear relative divergence tolerance + const Real _nl_rel_div_tol; + /// Divergence threshold value + const Real _div_threshold; + /// Number of iterations to force + unsigned int _nl_forced_its; + /// Maximum number of nonlinear ping-pong iterations for a solve + const unsigned int _nl_max_pingpong; + /// Current number of nonlinear ping-pong iterations for the current solve + unsigned int _nl_current_pingpong; +}; + +template +const T & +DefaultNonlinearConvergence::getSharedExecutionerParam(const std::string & param) +{ + const auto * executioner = getMooseApp().getExecutioner(); + if (executioner->isParamSetByUser(param)) + { + if (isParamSetByUser(param)) + _duplicate_shared_executioner_params.push_back(param); + return executioner->getParam(param); + } + else + return getParam(param); +} diff --git a/framework/include/convergence/IterationCountConvergence.h b/framework/include/convergence/IterationCountConvergence.h new file mode 100644 index 000000000000..e6d8d05c991a --- /dev/null +++ b/framework/include/convergence/IterationCountConvergence.h @@ -0,0 +1,44 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "Convergence.h" + +/** + * Checks convergence based on the iteration count. + */ +class IterationCountConvergence : public Convergence +{ +public: + static InputParameters validParams(); + + IterationCountConvergence(const InputParameters & parameters); + + virtual MooseConvergenceStatus checkConvergence(unsigned int iter) override final; + +protected: + /** + * Inner check of convergence. + * + * Derived classes are responsible for overriding this instead of \c checkConvergence(). + * + * @param[in] iter Iteration index + */ + virtual MooseConvergenceStatus checkConvergenceInner(unsigned int iter); + + /// Minimum number of iterations + const unsigned int _min_iterations; + + /// Maximum number of iterations + const unsigned int _max_iterations; + + /// Whether to converge at the maximum number of iterations + const bool _converge_at_max_iterations; +}; diff --git a/framework/include/convergence/PostprocessorConvergence.h b/framework/include/convergence/PostprocessorConvergence.h new file mode 100644 index 000000000000..30ab3f27c2e2 --- /dev/null +++ b/framework/include/convergence/PostprocessorConvergence.h @@ -0,0 +1,32 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "IterationCountConvergence.h" + +/** + * Compares a post-processor to a tolerance. + */ +class PostprocessorConvergence : public IterationCountConvergence +{ +public: + static InputParameters validParams(); + + PostprocessorConvergence(const InputParameters & parameters); + +protected: + virtual MooseConvergenceStatus checkConvergenceInner(unsigned int iter) override; + + /// Post-processor to use for convergence criteria + const PostprocessorValue & _postprocessor; + + /// Tolerance to which post-processor is compared + const Real _tol; +}; diff --git a/framework/include/convergence/ReferenceResidualConvergence.h b/framework/include/convergence/ReferenceResidualConvergence.h new file mode 100644 index 000000000000..b75317dc8268 --- /dev/null +++ b/framework/include/convergence/ReferenceResidualConvergence.h @@ -0,0 +1,144 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "DefaultNonlinearConvergence.h" +#include "ReferenceResidualInterface.h" + +// PETSc includes +#include +#include + +#include "libmesh/enum_norm_type.h" + +/** + * Uses a reference residual to define relative convergence criteria. + */ +class ReferenceResidualConvergence : public DefaultNonlinearConvergence, + public ReferenceResidualInterface +{ +public: + static InputParameters validParams(); + + ReferenceResidualConvergence(const InputParameters & parameters); + + /// Computes the reference residuals for each group + void updateReferenceResidual(); + + virtual void initialSetup() override; + + enum class NormalizationType + { + GLOBAL_L2 = 0, + LOCAL_L2 = 1, + GLOBAL_LINF = 2, + LOCAL_LINF = 3 + }; + + class ReferenceVectorTagIDKey + { + friend class TaggingInterface; + ReferenceVectorTagIDKey() {} + ReferenceVectorTagIDKey(const ReferenceVectorTagIDKey &) {} + }; + + /// Returns the tag ID associated with the reference vector tag ID key + TagID referenceVectorTagID(ReferenceVectorTagIDKey) const { return _reference_vector_tag_id; } + +protected: + virtual void nonlinearConvergenceSetup() override; + + virtual bool checkRelativeConvergence(const unsigned int it, + const Real fnorm, + const Real the_residual, + const Real rtol, + const Real abstol, + std::ostringstream & oss) override; + + /** + * Check the convergence by comparing the norm of each variable's residual separately against + * its reference variable's norm. Only consider the solution converged if all + * variables are converged individually using either a relative or absolute + * criterion. + * @param fnorm Function norm (norm of full residual vector) + * @param abstol Absolute convergence tolerance + * @param rtol Relative convergence tolerance + * @param initial_residual_before_preset_bcs Initial norm of full residual vector + * before applying preset bcs + * @return true if all variables are converged + */ + bool checkConvergenceIndividVars(const Real fnorm, + const Real abstol, + const Real rtol, + const Real initial_residual_before_preset_bcs); + + ///@{ + /// List of solution variable names whose reference residuals will be stored, + /// and the residual variable names that will store them. + std::vector _soln_var_names; + std::vector _ref_resid_var_names; + ///@} + + ///@{ + /// List of grouped solution variable names whose reference residuals will be stored, + /// and the residual variable names that will store them. + std::vector _group_soln_var_names; + std::vector _group_ref_resid_var_names; + ///@} + + ///@{ + /// Variable numbers associated with the names in _soln_var_names and _ref_resid_var_names. + std::vector _soln_vars; + std::vector _ref_resid_vars; + ///@} + + ///@{ + /// "Acceptable" absolute and relative tolerance multiplier and + /// acceptable number of iterations. Used when checking the + /// convergence of individual variables. + Real _accept_mult; + unsigned int _accept_iters; + ///@} + + ///@{ + /// Local storage for *discrete L2 residual norms* of the grouped variables listed in _group_ref_resid_var_names. + std::vector _group_ref_resid; + std::vector _group_resid; + std::vector _group_output_resid; + ///@} + + /// Group number index for each variable + std::vector _variable_group_num_index; + + /// Local storage for the scaling factors applied to each of the variables to apply to _ref_resid_vars. + std::vector _scaling_factors; + + /// The vector storing the reference residual values + const NumericVector * _reference_vector; + + /// Variables to use for individual variable convergence checks + std::vector _converge_on; + /// Flag for each solution variable being in 'converge_on' + std::vector _converge_on_var; + + /// Container for convergence treatment when the reference residual is zero + const enum class ZeroReferenceType { ZERO_TOLERANCE, RELATIVE_TOLERANCE } _zero_ref_type; + + /// Flag to optionally perform normalization of residual by reference residual before or after L2 norm is computed + bool _local_norm; + + /// Container for normalization type + FEMNormType _norm_type; + + /// The reference vector tag id + TagID _reference_vector_tag_id; + + bool _initialized; +}; diff --git a/framework/include/executioners/FEProblemSolve.h b/framework/include/executioners/FEProblemSolve.h index 568626c71de0..781e914ad38e 100644 --- a/framework/include/executioners/FEProblemSolve.h +++ b/framework/include/executioners/FEProblemSolve.h @@ -16,6 +16,7 @@ class FEProblemSolve : public NonlinearSolveObject public: FEProblemSolve(Executioner & ex); + static InputParameters feProblemDefaultConvergenceParams(); static InputParameters validParams(); static const std::set & mooseLineSearches(); diff --git a/framework/include/interfaces/ReferenceResidualInterface.h b/framework/include/interfaces/ReferenceResidualInterface.h new file mode 100644 index 000000000000..f99d04492e4a --- /dev/null +++ b/framework/include/interfaces/ReferenceResidualInterface.h @@ -0,0 +1,53 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "MooseTypes.h" +#include "InputParameters.h" + +class InputParameters; +class MooseObject; + +/** + * Interface class shared between ReferenceResidualProblem and ReferenceResidualConvergence. + */ +class ReferenceResidualInterface +{ +public: + static InputParameters validParams(); + + ReferenceResidualInterface(const MooseObject * moose_object); + + /** + * Add a set of variables that need to be grouped together. For use in + * actions that create variables. This is templated for backwards compatibility to allow passing + * in std::string or NonlinearVariableName. + * @tparam T string type used for variable names + * @param group_vars A set of solution variables that need to be grouped. + */ + template + void addGroupVariables(const std::set & group_vars); + +protected: + /// Name of variables that are grouped together to check convergence + std::vector> _group_variables; + + /// True if any variables are grouped + bool _use_group_variables; +}; + +template +void +ReferenceResidualInterface::addGroupVariables(const std::set & group_vars) +{ + _group_variables.push_back( + std::vector(group_vars.begin(), group_vars.end())); + _use_group_variables = true; +} diff --git a/framework/include/interfaces/TaggingInterface.h b/framework/include/interfaces/TaggingInterface.h index 1e8dceba838f..3a49b2768b30 100644 --- a/framework/include/interfaces/TaggingInterface.h +++ b/framework/include/interfaces/TaggingInterface.h @@ -26,7 +26,6 @@ class InputParameters; class MooseObject; class SubProblem; class Assembly; -class ReferenceResidualProblem; template InputParameters validParams(); diff --git a/framework/include/problems/FEProblemBase.h b/framework/include/problems/FEProblemBase.h index c50263707f84..812f213fa757 100644 --- a/framework/include/problems/FEProblemBase.h +++ b/framework/include/problems/FEProblemBase.h @@ -87,7 +87,7 @@ class LineSearch; class UserObject; class AutomaticMortarGeneration; class VectorPostprocessor; -class Function; +class Convergence; class MooseAppCoordTransform; class MortarUserObject; class SolutionInvalidity; @@ -100,26 +100,6 @@ class NonlinearImplicitSystem; class LinearImplicitSystem; } // namespace libMesh -/// Enumeration for nonlinear convergence reasons -enum class MooseNonlinearConvergenceReason -{ - ITERATING = 0, - CONVERGED_FNORM_ABS = 2, - CONVERGED_FNORM_RELATIVE = 3, - CONVERGED_SNORM_RELATIVE = 4, - DIVERGED_FUNCTION_COUNT = -2, - DIVERGED_FNORM_NAN = -4, - DIVERGED_LINE_SEARCH = -6, - DIVERGED_DTOL = -9, - DIVERGED_NL_RESIDUAL_PINGPONG = -10 -}; - -// The idea with these enums is to abstract the reasons for -// convergence/divergence, i.e. they could be used with linear algebra -// packages other than PETSc. They were directly inspired by PETSc, -// though. This enum could also be combined with the -// MooseNonlinearConvergenceReason enum but there might be some -// confusion (?) enum class MooseLinearConvergenceReason { ITERATING = 0, @@ -226,52 +206,6 @@ class FEProblemBase : public SubProblem, public Restartable std::vector> & nonlocalCouplingEntries(const THREAD_ID tid, const unsigned int nl_sys_num); - /** - * Check for convergence of the nonlinear solution - * @param msg Error message that gets sent back to the solver - * @param it Iteration counter - * @param xnorm Norm of the solution vector - * @param snorm Norm of the change in the solution vector - * @param fnorm Norm of the residual vector - * @param rtol Relative residual convergence tolerance - * @param divtol Relative residual divergence tolerance - * @param stol Solution change convergence tolerance - * @param abstol Absolute residual convergence tolerance - * @param nfuncs Number of function evaluations - * @param max_funcs Maximum Number of function evaluations - * @param div_threshold Maximum value of residual before triggering divergence check - */ - virtual MooseNonlinearConvergenceReason checkNonlinearConvergence(std::string & msg, - const PetscInt it, - const Real xnorm, - const Real snorm, - const Real fnorm, - const Real rtol, - const Real divtol, - const Real stol, - const Real abstol, - const PetscInt nfuncs, - const PetscInt max_funcs, - const Real div_threshold); - - /// Perform steps required before checking nonlinear convergence - virtual void nonlinearConvergenceSetup() {} - - /** - * Check the relative convergence of the nonlinear solution - * @param fnorm Norm of the residual vector - * @param the_residual The residual to check - * @param rtol Relative tolerance - * @param abstol Absolute tolerance - * @return Bool signifying convergence - */ - virtual bool checkRelativeConvergence(const PetscInt it, - const Real fnorm, - const Real the_residual, - const Real rtol, - const Real abstol, - std::ostringstream & oss); - virtual bool hasVariable(const std::string & var_name) const override; using SubProblem::getVariable; virtual const MooseVariableFieldBase & @@ -685,6 +619,43 @@ class FEProblemBase : public SubProblem, public Restartable /// Get a MeshDivision MeshDivision & getMeshDivision(const std::string & name, const THREAD_ID tid = 0) const; + /// Adds a Convergence object + virtual void + addConvergence(const std::string & type, const std::string & name, InputParameters & parameters); + /// Gets a Convergence object + virtual Convergence & getConvergence(const std::string & name, const THREAD_ID tid = 0) const; + /// Gets the Convergence objects + virtual const std::vector> & + getConvergenceObjects(const THREAD_ID tid = 0) const; + /// Returns true if the problem has a Convergence object of the given name + virtual bool hasConvergence(const std::string & name, const THREAD_ID tid = 0) const; + /// Returns true if the problem needs to add the default nonlinear convergence + bool needToAddDefaultNonlinearConvergence() const + { + return _need_to_add_default_nonlinear_convergence; + } + /// Sets _need_to_add_default_nonlinear_convergence to true + void setNeedToAddDefaultNonlinearConvergence() + { + _need_to_add_default_nonlinear_convergence = true; + } + /** + * Adds the default nonlinear Convergence associated with the problem + * + * This is called if the user does not supply 'nonlinear_convergence'. + * + * @param[in] params Parameters to apply to Convergence parameters + */ + virtual void addDefaultNonlinearConvergence(const InputParameters & params); + /** + * Returns true if an error will result if the user supplies 'nonlinear_convergence' + * + * Some problems are strongly tied to their convergence, and it does not make + * sense to use any convergence other than their default and additionally + * would be error-prone. + */ + virtual bool onlyAllowDefaultNonlinearConvergence() const { return false; } + /** * add a MOOSE line search */ @@ -2231,26 +2202,19 @@ class FEProblemBase : public SubProblem, public Restartable bool haveDisplaced() const override final { return _displaced_problem.get(); } - /// method setting the maximum number of allowable non linear residual pingpong - void setMaxNLPingPong(const unsigned int n_max_nl_pingpong) - { - _n_max_nl_pingpong = n_max_nl_pingpong; - } - - /// method setting the minimum number of nonlinear iterations before performing divergence checks - void setNonlinearForcedIterations(const unsigned int nl_forced_its) + /** + * Sets the nonlinear convergence object name if there is one + */ + void setNonlinearConvergenceName(const ConvergenceName & convergence_name) { - _nl_forced_its = nl_forced_its; + _nonlinear_convergence_name = convergence_name; + _set_nonlinear_convergence_name = true; } - /// method returning the number of forced nonlinear iterations - unsigned int getNonlinearForcedIterations() const { return _nl_forced_its; } - - /// method setting the absolute divergence tolerance - void setNonlinearAbsoluteDivergenceTolerance(const Real nl_abs_div_tol) - { - _nl_abs_div_tol = nl_abs_div_tol; - } + /** + * Gets the nonlinear convergence object name if there is one + */ + ConvergenceName getNonlinearConvergenceName() const; /** * Setter for whether we're computing the scaling jacobian @@ -2322,6 +2286,10 @@ class FEProblemBase : public SubProblem, public Restartable */ void setFailNextNonlinearConvergenceCheck() { _fail_next_nonlinear_convergence_check = true; } + /** + * Do not skip further residual evaluations and fail the next nonlinear convergence check + */ + void resetFailNextNonlinearConvergenceCheck() { _fail_next_nonlinear_convergence_check = false; } /* * Set the status of loop order of execution printing * @param print_exec set of execution flags to print on @@ -2430,6 +2398,9 @@ class FEProblemBase : public SubProblem, public Restartable protected: bool _initialized; + /// Nonlinear convergence name + ConvergenceName _nonlinear_convergence_name; + std::set _fe_vector_tags; std::set _fe_matrix_tags; @@ -2450,15 +2421,10 @@ class FEProblemBase : public SubProblem, public Restartable Real & _dt; Real & _dt_old; - /// maximum numbver - unsigned int _n_nl_pingpong = 0; - unsigned int _n_max_nl_pingpong = std::numeric_limits::max(); - - /// the number of forced nonlinear iterations - int _nl_forced_its = 0; - - /// the absolute non linear divergence tolerance - Real _nl_abs_div_tol = -1; + /// Flag that the nonlinear convergence name has been set + bool _set_nonlinear_convergence_name; + /// Flag that the problem needs to add the default nonlinear convergence + bool _need_to_add_default_nonlinear_convergence; /// The linear system names const std::vector _linear_sys_names; @@ -2529,6 +2495,9 @@ class FEProblemBase : public SubProblem, public Restartable /// functions MooseObjectWarehouse _functions; + /// convergence warehouse + MooseObjectWarehouse _convergences; + /// nonlocal kernels MooseObjectWarehouse _nonlocal_kernels; diff --git a/framework/include/problems/ReferenceResidualProblem.h b/framework/include/problems/ReferenceResidualProblem.h index 7b7f3c84320b..3e7e445b4e3d 100644 --- a/framework/include/problems/ReferenceResidualProblem.h +++ b/framework/include/problems/ReferenceResidualProblem.h @@ -10,147 +10,19 @@ #pragma once #include "FEProblem.h" -#include "libmesh/enum_norm_type.h" +#include "ReferenceResidualInterface.h" /** - * FEProblemBase derived class to enable convergence checking relative to a user-specified - * postprocessor + * Problem that checks for convergence relative to a user-supplied reference quantity + * rather than the initial residual. */ -class ReferenceResidualProblem : public FEProblem +class ReferenceResidualProblem : public FEProblem, public ReferenceResidualInterface { public: static InputParameters validParams(); ReferenceResidualProblem(const InputParameters & params); - virtual void initialSetup() override; - - void updateReferenceResidual(); - - virtual void nonlinearConvergenceSetup() override; - - virtual bool checkRelativeConvergence(const PetscInt it, - const Real fnorm, - const Real the_residual, - const Real rtol, - const Real abstol, - std::ostringstream & oss) override; - - /** - * Check the convergence by comparing the norm of each variable separately against - * its reference variable's norm. Only consider the solution converged if all - * variables are converged individually using either a relative or absolute - * criterion. - * @param fnorm Function norm (norm of full residual vector) - * @param abstol Absolute convergence tolerance - * @param rtol Relative convergence tolerance - * @param initial_residual_before_preset_bcs Initial norm of full residual vector - * before applying preset bcs - * @return true if all variables are converged - */ - bool checkConvergenceIndividVars(const Real fnorm, - const Real abstol, - const Real rtol, - const Real initial_residual_before_preset_bcs); - - /** - * Add a set of variables that need to be grouped together. For use in - * actions that create variables. This is templated for backwards compatibility to allow passing - * in std::string or NonlinearVariableName. - * @param group_vars A set of solution variables that need to be grouped. - */ - template - void addGroupVariables(const std::set & group_vars); - - enum class NormalizationType - { - GLOBAL_L2, - LOCAL_L2, - GLOBAL_LINF, - LOCAL_LINF - }; - - class ReferenceVectorTagIDKey - { - friend class TaggingInterface; - ReferenceVectorTagIDKey() {} - ReferenceVectorTagIDKey(const ReferenceVectorTagIDKey &) {} - }; - - TagID referenceVectorTagID(ReferenceVectorTagIDKey) const { return _reference_vector_tag_id; } - -protected: - ///@{ - /// List of solution variable names whose reference residuals will be stored, - /// and the residual variable names that will store them. - std::vector _soln_var_names; - std::vector _ref_resid_var_names; - ///@} - - ///@{ - /// List of grouped solution variable names whose reference residuals will be stored, - /// and the residual variable names that will store them. - std::vector _group_soln_var_names; - std::vector _group_ref_resid_var_names; - ///@} - - ///@{ - /// Variable numbers assoicated with the names in _soln_var_names and _ref_resid_var_names. - std::vector _soln_vars; - std::vector _ref_resid_vars; - ///@} - - ///@{ - /// "Acceptable" absolute and relative tolerance multiplier and - /// acceptable number of iterations. Used when checking the - /// convergence of individual variables. - Real _accept_mult; - int _accept_iters; - ///@} - - ///@{ - /// Local storage for *discrete L2 residual norms* of the grouped variables listed in _group_ref_resid_var_names. - std::vector _group_ref_resid; - std::vector _group_resid; - std::vector _group_output_resid; - ///@} - - /// Group number index for each variable - std::vector _variable_group_num_index; - - /// Local storage for the scaling factors applied to each of the variables to apply to _ref_resid_vars. - std::vector _scaling_factors; - - /// Name of variables that are grouped together to check convergence - std::vector> _group_variables; - - /// True if any variables are grouped - bool _use_group_variables; - - /// The vector storing the reference residual values - const NumericVector * _reference_vector; - - std::vector _converge_on; - std::vector _converge_on_var; - - /// Flag to optionally perform normalization of residual by reference residual before or after L2 norm is computed - bool _local_norm; - - /// Container for normalization type - FEMNormType _norm_type; - - /// Container for convergence treatment when the reference residual is zero - const enum class ZeroReferenceType { ZERO_TOLERANCE, RELATIVE_TOLERANCE } _zero_ref_type; - - /// The reference vector tag id - TagID _reference_vector_tag_id; + virtual void addDefaultNonlinearConvergence(const InputParameters & params) override; + virtual bool onlyAllowDefaultNonlinearConvergence() const override { return true; } }; - -template -void -ReferenceResidualProblem::addGroupVariables(const std::set & group_vars) -{ - _group_variables.push_back( - std::vector(group_vars.begin(), group_vars.end())); - _use_group_variables = true; -} diff --git a/framework/include/utils/MooseTypes.h b/framework/include/utils/MooseTypes.h index a5e91e45d924..52632ce838c0 100644 --- a/framework/include/utils/MooseTypes.h +++ b/framework/include/utils/MooseTypes.h @@ -1149,6 +1149,9 @@ DerivativeStringClass(ParsedFunctionExpression); /// System name support of multiple nonlinear systems on the same mesh DerivativeStringClass(NonlinearSystemName); +/// Name of a Convergence object +DerivativeStringClass(ConvergenceName); + /// System name support of multiple linear systems on the same mesh DerivativeStringClass(LinearSystemName); diff --git a/framework/src/actions/AddConvergenceAction.C b/framework/src/actions/AddConvergenceAction.C new file mode 100644 index 000000000000..f390720819ae --- /dev/null +++ b/framework/src/actions/AddConvergenceAction.C @@ -0,0 +1,32 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#include "AddConvergenceAction.h" +#include "FEProblem.h" + +registerMooseAction("MooseApp", AddConvergenceAction, "add_convergence"); + +InputParameters +AddConvergenceAction::validParams() +{ + InputParameters params = MooseObjectAction::validParams(); + params.addClassDescription("Add a Convergence object to the simulation."); + return params; +} + +AddConvergenceAction::AddConvergenceAction(const InputParameters & params) + : MooseObjectAction(params) +{ +} + +void +AddConvergenceAction::act() +{ + _problem->addConvergence(_type, _name, _moose_object_pars); +} diff --git a/framework/src/actions/AddDefaultConvergenceAction.C b/framework/src/actions/AddDefaultConvergenceAction.C new file mode 100644 index 000000000000..3b9d29de35f4 --- /dev/null +++ b/framework/src/actions/AddDefaultConvergenceAction.C @@ -0,0 +1,38 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#include "AddDefaultConvergenceAction.h" +#include "FEProblem.h" +#include "Executioner.h" + +registerMooseAction("MooseApp", AddDefaultConvergenceAction, "add_default_convergence"); + +InputParameters +AddDefaultConvergenceAction::validParams() +{ + InputParameters params = Action::validParams(); + params.addClassDescription("Add a default Convergence object to the simulation."); + return params; +} + +AddDefaultConvergenceAction::AddDefaultConvergenceAction(const InputParameters & params) + : Action(params) +{ +} + +void +AddDefaultConvergenceAction::act() +{ + if (_problem->needToAddDefaultNonlinearConvergence()) + { + const std::string default_name = "default_nonlinear_convergence"; + _problem->setNonlinearConvergenceName(default_name); + _problem->addDefaultNonlinearConvergence(getMooseApp().getExecutioner()->parameters()); + } +} diff --git a/framework/src/base/Moose.C b/framework/src/base/Moose.C index 9343de8feb89..a2b6833e6356 100644 --- a/framework/src/base/Moose.C +++ b/framework/src/base/Moose.C @@ -172,6 +172,7 @@ addActionTypes(Syntax & syntax) registerTask ("compose_time_stepper", true); registerMooseObjectTask("setup_time_integrators", TimeIntegrator, false); registerMooseObjectTask("setup_time_integrator", TimeIntegrator, false); + registerMooseObjectTask("add_convergence", Convergence, false); registerMooseObjectTask("add_preconditioning", MoosePreconditioner, false); registerMooseObjectTask("add_field_split", Split, false); @@ -278,6 +279,8 @@ addActionTypes(Syntax & syntax) registerTask("create_problem_custom", false); registerTask("create_problem_complete", false); + registerTask("add_default_convergence", true); + // Action for setting up the signal-based checkpoint registerTask("auto_checkpoint_action", true); /**************************/ @@ -334,6 +337,7 @@ addActionTypes(Syntax & syntax) "(setup_component)" // no particular reason for that placement "(read_executor)" "(add_executor)" + "(add_default_convergence)" "(check_integrity_early)" "(check_integrity_early_physics)" "(setup_predictor)" @@ -342,6 +346,7 @@ addActionTypes(Syntax & syntax) "(add_mortar_variable)" "(setup_variable_complete)" "(setup_quadrature)" + "(add_convergence)" "(add_periodic_bc)" "(add_user_object, add_corrector, add_mesh_modifier)" "(add_distribution)" @@ -495,6 +500,8 @@ associateSyntaxInner(Syntax & syntax, ActionFactory & /*action_factory*/) registerSyntax("AddMeshDivisionAction", "MeshDivisions/*"); syntax.registerSyntaxType("MeshDivisions/*", "MeshDivisionName"); + registerSyntax("AddConvergenceAction", "Convergence/*"); + syntax.registerSyntaxType("Convergence/*", "ConvergenceName"); registerSyntax("GlobalParamsAction", "GlobalParams"); diff --git a/framework/src/convergence/Convergence.C b/framework/src/convergence/Convergence.C new file mode 100644 index 000000000000..0f6fc0f980d5 --- /dev/null +++ b/framework/src/convergence/Convergence.C @@ -0,0 +1,32 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#include "Convergence.h" + +InputParameters +Convergence::validParams() +{ + InputParameters params = MooseObject::validParams(); + params += SetupInterface::validParams(); + params += PostprocessorInterface::validParams(); + params += PerfGraphInterface::validParams(); + + params.registerBase("Convergence"); + + return params; +} + +Convergence::Convergence(const InputParameters & parameters) + : MooseObject(parameters), + SetupInterface(this), + PostprocessorInterface(this), + PerfGraphInterface(this), + _perfid_check_convergence(registerTimedSection("checkConvergence", 5, "Checking Convergence")) +{ +} diff --git a/framework/src/convergence/DefaultNonlinearConvergence.C b/framework/src/convergence/DefaultNonlinearConvergence.C new file mode 100644 index 000000000000..0ebe8da17d06 --- /dev/null +++ b/framework/src/convergence/DefaultNonlinearConvergence.C @@ -0,0 +1,261 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +// MOOSE includes +#include "DefaultNonlinearConvergence.h" +#include "FEProblemBase.h" +#include "PetscSupport.h" +#include "NonlinearSystemBase.h" + +#include "libmesh/equation_systems.h" + +// PETSc includes +#include +#include +#include + +registerMooseObject("MooseApp", DefaultNonlinearConvergence); + +InputParameters +DefaultNonlinearConvergence::validParams() +{ + InputParameters params = Convergence::validParams(); + params += FEProblemSolve::feProblemDefaultConvergenceParams(); + + params.addPrivateParam("added_as_default", false); + + params.addClassDescription("Default convergence criteria for FEProblem."); + + return params; +} + +DefaultNonlinearConvergence::DefaultNonlinearConvergence(const InputParameters & parameters) + : Convergence(parameters), + _added_as_default(getParam("added_as_default")), + _fe_problem(*getCheckedPointerParam("_fe_problem_base")), + _nl_abs_div_tol(getSharedExecutionerParam("nl_abs_div_tol")), + _nl_rel_div_tol(getSharedExecutionerParam("nl_div_tol")), + _div_threshold(std::numeric_limits::max()), + _nl_forced_its(getSharedExecutionerParam("nl_forced_its")), + _nl_max_pingpong(getSharedExecutionerParam("n_max_nonlinear_pingpong")), + _nl_current_pingpong(0) +{ + EquationSystems & es = _fe_problem.es(); + + es.parameters.set("nonlinear solver maximum iterations") = + getSharedExecutionerParam("nl_max_its"); + es.parameters.set("nonlinear solver maximum function evaluations") = + getSharedExecutionerParam("nl_max_funcs"); + es.parameters.set("nonlinear solver absolute residual tolerance") = + getSharedExecutionerParam("nl_abs_tol"); + es.parameters.set("nonlinear solver relative residual tolerance") = + getSharedExecutionerParam("nl_rel_tol"); + es.parameters.set("nonlinear solver divergence tolerance") = + getSharedExecutionerParam("nl_div_tol"); + es.parameters.set("nonlinear solver relative step tolerance") = + getSharedExecutionerParam("nl_rel_step_tol"); +} + +void +DefaultNonlinearConvergence::initialSetup() +{ + Convergence::initialSetup(); + + checkDuplicateSetSharedExecutionerParams(); +} + +bool +DefaultNonlinearConvergence::checkRelativeConvergence(const unsigned int /*it*/, + const Real fnorm, + const Real ref_norm, + const Real rel_tol, + const Real /*abs_tol*/, + std::ostringstream & oss) +{ + if (fnorm <= ref_norm * rel_tol) + { + oss << "Converged due to residual norm " << fnorm << " < relative tolerance (" << rel_tol + << ")\n"; + return true; + } + else + return false; +} + +Convergence::MooseConvergenceStatus +DefaultNonlinearConvergence::checkConvergence(unsigned int iter) +{ + TIME_SECTION(_perfid_check_convergence); + + NonlinearSystemBase & system = _fe_problem.currentNonlinearSystem(); + MooseConvergenceStatus status = MooseConvergenceStatus::ITERATING; + + // Needed by ResidualReferenceConvergence + nonlinearConvergenceSetup(); + + SNES snes = system.getSNES(); + + PetscErrorCode ierr; + + // ||u|| + PetscReal xnorm; + ierr = SNESGetSolutionNorm(snes, &xnorm); + CHKERRABORT(_fe_problem.comm().get(), ierr); + + // ||r|| + PetscReal fnorm; + ierr = SNESGetFunctionNorm(snes, &fnorm); + CHKERRABORT(_fe_problem.comm().get(), ierr); + + // ||du|| + PetscReal snorm; + ierr = SNESGetUpdateNorm(snes, &snorm); + CHKERRABORT(_fe_problem.comm().get(), ierr); + + // Get current number of function evaluations done by SNES + PetscInt nfuncs; + ierr = SNESGetNumberFunctionEvals(snes, &nfuncs); + CHKERRABORT(_fe_problem.comm().get(), ierr); + + // Get tolerances from SNES + PetscReal abs_tol, rel_tol, rel_step_tol; + PetscInt max_its, max_funcs; + ierr = SNESGetTolerances(snes, &abs_tol, &rel_tol, &rel_step_tol, &max_its, &max_funcs); + CHKERRABORT(_fe_problem.comm().get(), ierr); + +#if !PETSC_VERSION_LESS_THAN(3, 8, 4) + PetscBool force_iteration = PETSC_FALSE; + ierr = SNESGetForceIteration(snes, &force_iteration); + CHKERRABORT(_fe_problem.comm().get(), ierr); + + // if PETSc says to force iteration, then force at least one iteration + if (force_iteration && !(_nl_forced_its)) + _nl_forced_its = 1; + + // if specified here to force iteration, but PETSc doesn't know, tell it + if (!force_iteration && (_nl_forced_its)) + { + ierr = SNESSetForceIteration(snes, PETSC_TRUE); + CHKERRABORT(_fe_problem.comm().get(), ierr); + } +#endif + + // See if SNESSetFunctionDomainError() has been called. Note: + // SNESSetFunctionDomainError() and SNESGetFunctionDomainError() + // were added in different releases of PETSc. + PetscBool domainerror; + ierr = SNESGetFunctionDomainError(snes, &domainerror); + CHKERRABORT(_fe_problem.comm().get(), ierr); + if (domainerror) + status = MooseConvergenceStatus::DIVERGED; + + Real fnorm_old; + // This is the first residual before any iterations have been done, but after + // solution-modifying objects (if any) have been imposed on the solution vector. + // We save it, and use it to detect convergence if system.usePreSMOResidual() == false. + if (iter == 0) + { + system.setInitialResidual(fnorm); + fnorm_old = fnorm; + _nl_current_pingpong = 0; + } + else + fnorm_old = system._last_nl_rnorm; + + // Check for nonlinear residual ping-pong. + // Ping-pong will always start from a residual increase + if ((_nl_current_pingpong % 2 == 1 && !(fnorm > fnorm_old)) || + (_nl_current_pingpong % 2 == 0 && fnorm > fnorm_old)) + _nl_current_pingpong += 1; + else + _nl_current_pingpong = 0; + + std::ostringstream oss; + if (fnorm != fnorm) + { + oss << "Failed to converge, residual norm is NaN\n"; + status = MooseConvergenceStatus::DIVERGED; + } + else if ((iter >= _nl_forced_its) && fnorm < abs_tol) + { + oss << "Converged due to residual norm " << fnorm << " < " << abs_tol << '\n'; + status = MooseConvergenceStatus::CONVERGED; + } + else if (nfuncs >= max_funcs) + { + oss << "Exceeded maximum number of residual evaluations: " << nfuncs << " > " << max_funcs + << '\n'; + status = MooseConvergenceStatus::DIVERGED; + } + else if ((iter >= _nl_forced_its) && iter && fnorm > system._last_nl_rnorm && + fnorm >= _div_threshold) + { + oss << "Nonlinear solve was blowing up!\n"; + status = MooseConvergenceStatus::DIVERGED; + } + if ((iter >= _nl_forced_its) && iter && status == MooseConvergenceStatus::ITERATING) + { + const auto ref_residual = system.referenceResidual(); + if (checkRelativeConvergence(iter, fnorm, ref_residual, rel_tol, abs_tol, oss)) + status = MooseConvergenceStatus::CONVERGED; + else if (snorm < rel_step_tol * xnorm) + { + oss << "Converged due to small update length: " << snorm << " < " << rel_step_tol << " * " + << xnorm << '\n'; + status = MooseConvergenceStatus::CONVERGED; + } + else if (_nl_rel_div_tol > 0 && fnorm > ref_residual * _nl_rel_div_tol) + { + oss << "Diverged due to relative residual " << ref_residual << " > divergence tolerance " + << _nl_rel_div_tol << " * relative residual " << ref_residual << '\n'; + status = MooseConvergenceStatus::DIVERGED; + } + else if (_nl_abs_div_tol > 0 && fnorm > _nl_abs_div_tol) + { + oss << "Diverged due to residual " << fnorm << " > absolute divergence tolerance " + << _nl_abs_div_tol << '\n'; + status = MooseConvergenceStatus::DIVERGED; + } + else if (_nl_current_pingpong > _nl_max_pingpong) + { + oss << "Diverged due to maximum nonlinear residual pingpong achieved" << '\n'; + status = MooseConvergenceStatus::DIVERGED; + } + } + + system._last_nl_rnorm = fnorm; + system._current_nl_its = static_cast(iter); + + std::string msg; + msg = oss.str(); + if (_app.multiAppLevel() > 0) + MooseUtils::indentMessage(_app.name(), msg); + if (msg.length() > 0) +#if !PETSC_VERSION_LESS_THAN(3, 17, 0) + ierr = PetscInfo(snes, "%s", msg.c_str()); +#else + ierr = PetscInfo(snes, msg.c_str()); +#endif + + return status; +} + +void +DefaultNonlinearConvergence::checkDuplicateSetSharedExecutionerParams() const +{ + if (_duplicate_shared_executioner_params.size() > 0 && !_added_as_default) + { + std::ostringstream oss; + oss << "The following parameters were set in both this Convergence object and the " + "executioner:\n"; + for (const auto & param : _duplicate_shared_executioner_params) + oss << " " << param << "\n"; + mooseError(oss.str()); + } +} diff --git a/framework/src/convergence/IterationCountConvergence.C b/framework/src/convergence/IterationCountConvergence.C new file mode 100644 index 000000000000..92779dc713ac --- /dev/null +++ b/framework/src/convergence/IterationCountConvergence.C @@ -0,0 +1,78 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#include "IterationCountConvergence.h" + +registerMooseObject("MooseApp", IterationCountConvergence); + +InputParameters +IterationCountConvergence::validParams() +{ + InputParameters params = Convergence::validParams(); + + params.addParam("min_iterations", 0, "Minimum number of iterations"); + params.addParam("max_iterations", 50, "Maximum number of iterations"); + params.addParam( + "converge_at_max_iterations", false, "Converge at 'max_iterations' instead of diverging"); + + params.addClassDescription("Checks the iteration count."); + + return params; +} + +IterationCountConvergence::IterationCountConvergence(const InputParameters & parameters) + : Convergence(parameters), + _min_iterations(getParam("min_iterations")), + _max_iterations(getParam("max_iterations")), + _converge_at_max_iterations(getParam("converge_at_max_iterations")) +{ + if (_max_iterations < _min_iterations) + mooseError("'max_iterations' must be >= 'min_iterations'."); +} + +Convergence::MooseConvergenceStatus +IterationCountConvergence::checkConvergence(unsigned int iter) +{ + const auto status_inner = checkConvergenceInner(iter); + + switch (status_inner) + { + case MooseConvergenceStatus::ITERATING: + if (iter >= _max_iterations) + { + if (_converge_at_max_iterations) + return MooseConvergenceStatus::CONVERGED; + else + return MooseConvergenceStatus::DIVERGED; + } + else + return MooseConvergenceStatus::ITERATING; + break; + + case MooseConvergenceStatus::CONVERGED: + if (iter < _min_iterations) + return MooseConvergenceStatus::ITERATING; + else + return MooseConvergenceStatus::CONVERGED; + break; + + case MooseConvergenceStatus::DIVERGED: + return MooseConvergenceStatus::DIVERGED; + break; + + default: + mooseError("Invalid convergence status"); + } +} + +Convergence::MooseConvergenceStatus +IterationCountConvergence::checkConvergenceInner(unsigned int /*iter*/) +{ + return MooseConvergenceStatus::ITERATING; +} diff --git a/framework/src/convergence/PostprocessorConvergence.C b/framework/src/convergence/PostprocessorConvergence.C new file mode 100644 index 000000000000..8daff2018aee --- /dev/null +++ b/framework/src/convergence/PostprocessorConvergence.C @@ -0,0 +1,42 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#include "PostprocessorConvergence.h" + +registerMooseObject("MooseApp", PostprocessorConvergence); + +InputParameters +PostprocessorConvergence::validParams() +{ + InputParameters params = IterationCountConvergence::validParams(); + + params.addClassDescription("Compares the absolute value of a post-processor to a tolerance."); + + params.addRequiredParam("postprocessor", + "Post-processor to use for convergence criteria"); + params.addRequiredParam("tolerance", "Tolerance to use for convergence criteria"); + + return params; +} + +PostprocessorConvergence::PostprocessorConvergence(const InputParameters & parameters) + : IterationCountConvergence(parameters), + _postprocessor(getPostprocessorValue("postprocessor")), + _tol(getParam("tolerance")) +{ +} + +Convergence::MooseConvergenceStatus +PostprocessorConvergence::checkConvergenceInner(unsigned int /*iter*/) +{ + if (std::abs(_postprocessor) <= _tol) + return MooseConvergenceStatus::CONVERGED; + else + return MooseConvergenceStatus::ITERATING; +} diff --git a/framework/src/convergence/ReferenceResidualConvergence.C b/framework/src/convergence/ReferenceResidualConvergence.C new file mode 100644 index 000000000000..d884c5366046 --- /dev/null +++ b/framework/src/convergence/ReferenceResidualConvergence.C @@ -0,0 +1,530 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +// MOOSE includes +#include "ReferenceResidualConvergence.h" +#include "ReferenceResidualProblem.h" +#include "FEProblemBase.h" +#include "PetscSupport.h" +#include "Executioner.h" +#include "NonlinearSystemBase.h" +#include "TaggingInterface.h" +#include "AuxiliarySystem.h" +#include "MooseVariableScalar.h" +#include "NonlinearSystem.h" + +// PETSc includes +#include + +registerMooseObject("MooseApp", ReferenceResidualConvergence); + +InputParameters +ReferenceResidualConvergence::validParams() +{ + InputParameters params = DefaultNonlinearConvergence::validParams(); + params += ReferenceResidualInterface::validParams(); + + params.addClassDescription( + "Check the convergence of a problem with respect to a user-supplied reference solution." + " Replaces ReferenceResidualProblem, currently still used in conjunction with it."); + + return params; +} + +ReferenceResidualConvergence::ReferenceResidualConvergence(const InputParameters & parameters) + : DefaultNonlinearConvergence(parameters), + ReferenceResidualInterface(this), + _reference_vector(nullptr), + _converge_on(getParam>("converge_on")), + _zero_ref_type( + getParam("zero_reference_residual_treatment").getEnum()), + _reference_vector_tag_id(Moose::INVALID_TAG_ID), + _initialized(false) +{ + if (parameters.isParamValid("solution_variables")) + { + if (parameters.isParamValid("reference_vector")) + mooseDeprecated("The `solution_variables` parameter is deprecated, has no effect when " + "the tagging system is used, and will be removed on January 1, 2020. " + "Please simply delete this parameter from your input file."); + _soln_var_names = parameters.get>("solution_variables"); + } + + if (parameters.isParamValid("reference_residual_variables") && + parameters.isParamValid("reference_vector")) + mooseError( + "You may specify either the `reference_residual_variables` " + "or `reference_vector` parameter, not both. `reference_residual_variables` is deprecated " + "so we recommend using `reference_vector`"); + + if (parameters.isParamValid("reference_residual_variables")) + { + mooseDeprecated( + "The save-in method for composing reference residual quantities is deprecated " + "and will be removed on January 1, 2020. Please use the tagging system instead; " + "specifically, please assign a TagName to the `reference_vector` parameter"); + + _ref_resid_var_names = + parameters.get>("reference_residual_variables"); + + if (_soln_var_names.size() != _ref_resid_var_names.size()) + mooseError("Size of solution_variables (", + _soln_var_names.size(), + ") != size of reference_residual_variables (", + _ref_resid_var_names.size(), + ")"); + } + else if (parameters.isParamValid("reference_vector")) + { + if (_fe_problem.numNonlinearSystems() > 1) + paramError( + "nl_sys_names", + "reference residual problem does not currently support multiple nonlinear systems"); + _reference_vector_tag_id = _fe_problem.getVectorTagID(getParam("reference_vector")); + _reference_vector = &_fe_problem.getNonlinearSystemBase(0).getVector(_reference_vector_tag_id); + } + else + mooseInfo("Neither the `reference_residual_variables` nor `reference_vector` parameter is " + "specified, which means that no reference " + "quantites are set. Because of this, the standard technique of comparing the " + "norm of the full residual vector with its initial value will be used."); + + _accept_mult = parameters.get("acceptable_multiplier"); + _accept_iters = parameters.get("acceptable_iterations"); + + const auto norm_type_enum = + parameters.get("normalization_type").getEnum(); + if (norm_type_enum == NormalizationType::LOCAL_L2) + { + _norm_type = DISCRETE_L2; + _local_norm = true; + } + else if (norm_type_enum == NormalizationType::GLOBAL_L2) + { + _norm_type = DISCRETE_L2; + _local_norm = false; + } + else if (norm_type_enum == NormalizationType::LOCAL_LINF) + { + _norm_type = DISCRETE_L_INF; + _local_norm = true; + } + else if (norm_type_enum == NormalizationType::GLOBAL_LINF) + { + _norm_type = DISCRETE_L_INF; + _local_norm = false; + } + else + { + mooseAssert(false, "This point should not be reached."); + } + + if (_local_norm && !parameters.isParamValid("reference_vector")) + paramError("reference_vector", "If local norm is used, a reference_vector must be provided."); +} + +void +ReferenceResidualConvergence::initialSetup() +{ + DefaultNonlinearConvergence::initialSetup(); + + NonlinearSystemBase & nonlinear_sys = _fe_problem.getNonlinearSystemBase(/*nl_sys=*/0); + AuxiliarySystem & aux_sys = _fe_problem.getAuxiliarySystem(); + System & s = nonlinear_sys.system(); + auto & as = aux_sys.sys(); + + if (_soln_var_names.empty()) + { + // If the user provides reference_vector, that implies that they want the + // individual variables compared against their reference quantities in the + // tag vector. The code depends on having _soln_var_names populated, + // so fill that out if they didn't specify solution_variables. + if (_reference_vector) + for (unsigned int var_num = 0; var_num < s.n_vars(); var_num++) + _soln_var_names.push_back(s.variable_name(var_num)); + + // If they didn't provide reference_vector, that implies that they + // want to skip the individual variable comparison, so leave it alone. + } + else if (_soln_var_names.size() != s.n_vars()) + mooseError("Size of solution_variables (", + _soln_var_names.size(), + ") != number of variables in system (", + s.n_vars(), + ")"); + + const auto n_soln_vars = _soln_var_names.size(); + _variable_group_num_index.resize(n_soln_vars); + + if (!_converge_on.empty()) + { + _converge_on_var.assign(n_soln_vars, false); + for (std::size_t i = 0; i < n_soln_vars; ++i) + for (const auto & c : _converge_on) + if (MooseUtils::globCompare(_soln_var_names[i], c)) + { + _converge_on_var[i] = true; + break; + } + } + else + _converge_on_var.assign(n_soln_vars, true); + + unsigned int group_variable_num = 0; + if (_use_group_variables) + { + for (unsigned int i = 0; i < _group_variables.size(); ++i) + { + group_variable_num += _group_variables[i].size(); + if (_group_variables[i].size() == 1) + mooseError(" In the 'group_variables' parameter, variable ", + _group_variables[i][0], + " is not grouped with other variables."); + } + + unsigned int size = n_soln_vars - group_variable_num + _group_variables.size(); + _group_ref_resid.resize(size); + _group_resid.resize(size); + _group_output_resid.resize(size); + _group_soln_var_names.resize(size); + _group_ref_resid_var_names.resize(size); + } + // If not using groups, use one group for each variable + else + { + _group_ref_resid.resize(n_soln_vars); + _group_resid.resize(n_soln_vars); + _group_output_resid.resize(n_soln_vars); + _group_soln_var_names.resize(n_soln_vars); + _group_ref_resid_var_names.resize(n_soln_vars); + } + + std::set check_duplicate; + if (_use_group_variables) + { + for (unsigned int i = 0; i < _group_variables.size(); ++i) + for (unsigned int j = 0; j < _group_variables[i].size(); ++j) + check_duplicate.insert(_group_variables[i][j]); + + if (check_duplicate.size() != group_variable_num) + mooseError( + "A variable cannot be included in multiple groups in the 'group_variables' parameter."); + } + + _soln_vars.clear(); + for (unsigned int i = 0; i < n_soln_vars; ++i) + { + bool found_match = false; + for (unsigned int var_num = 0; var_num < s.n_vars(); var_num++) + if (_soln_var_names[i] == s.variable_name(var_num)) + { + _soln_vars.push_back(var_num); + found_match = true; + break; + } + + if (!found_match) + mooseError("Could not find solution variable '", + _soln_var_names[i], + "' in system '", + s.name(), + "'."); + } + + if (!_reference_vector) + { + _ref_resid_vars.clear(); + for (unsigned int i = 0; i < _ref_resid_var_names.size(); ++i) + { + bool foundMatch = false; + for (unsigned int var_num = 0; var_num < as.n_vars(); var_num++) + if (_ref_resid_var_names[i] == as.variable_name(var_num)) + { + _ref_resid_vars.push_back(var_num); + foundMatch = true; + break; + } + + if (!foundMatch) + mooseError("Could not find variable '", _ref_resid_var_names[i], "' in auxiliary system"); + } + } + + unsigned int ungroup_index = 0; + if (_use_group_variables) + ungroup_index = _group_variables.size(); + + for (const auto i : index_range(_soln_vars)) + { + bool find_group = false; + if (_use_group_variables) + { + for (unsigned int j = 0; j < _group_variables.size(); ++j) + if (std::find(_group_variables[j].begin(), + _group_variables[j].end(), + s.variable_name(_soln_vars[i])) != _group_variables[j].end()) + { + if (!_converge_on_var[i]) + paramError("converge_on", + "You added variable '", + _soln_var_names[i], + "' to a group but excluded it from the convergence check. This is not " + "permitted."); + + _variable_group_num_index[i] = j; + find_group = true; + break; + } + + if (!find_group) + { + _variable_group_num_index[i] = ungroup_index; + ungroup_index++; + } + } + else + _variable_group_num_index[i] = i; + } + + if (_use_group_variables) + { + // Check for variable groups containing both field and scalar variables + for (unsigned int i = 0; i < _group_variables.size(); ++i) + { + unsigned int num_scalar_vars = 0; + unsigned int num_field_vars = 0; + if (_group_variables[i].size() > 1) + { + for (unsigned int j = 0; j < _group_variables[i].size(); ++j) + for (unsigned int var_num = 0; var_num < s.n_vars(); var_num++) + if (_group_variables[i][j] == s.variable_name(var_num)) + { + if (nonlinear_sys.isScalarVariable(_soln_vars[var_num])) + ++num_scalar_vars; + else + ++num_field_vars; + break; + } + } + if (num_scalar_vars > 0 && num_field_vars > 0) + mooseWarning("In the 'group_variables' parameter, standard variables and scalar variables " + "are grouped together in group ", + i); + } + } + + // Keep track of the names of the variables in each group (of 1 variable if not using groups) + for (unsigned int i = 0; i < n_soln_vars; ++i) + { + if (_group_soln_var_names[_variable_group_num_index[i]].empty()) + { + _group_soln_var_names[_variable_group_num_index[i]] = _soln_var_names[i]; + if (_use_group_variables && _variable_group_num_index[i] < _group_variables.size()) + _group_soln_var_names[_variable_group_num_index[i]] += " (grouped) "; + } + + if (!_reference_vector && _group_ref_resid_var_names[_variable_group_num_index[i]].empty()) + { + _group_ref_resid_var_names[_variable_group_num_index[i]] = _ref_resid_var_names[i]; + if (_use_group_variables && _variable_group_num_index[i] < _group_variables.size()) + _group_ref_resid_var_names[_variable_group_num_index[i]] += " (grouped) "; + } + } + + if (!_reference_vector) + { + const unsigned int size_soln_vars = _soln_vars.size(); + _scaling_factors.resize(size_soln_vars); + for (unsigned int i = 0; i < size_soln_vars; ++i) + if (nonlinear_sys.isScalarVariable(_soln_vars[i])) + _scaling_factors[i] = nonlinear_sys.getScalarVariable(0, _soln_vars[i]).scalingFactor(); + else + _scaling_factors[i] = nonlinear_sys.getVariable(/*tid*/ 0, _soln_vars[i]).scalingFactor(); + } + _initialized = true; +} + +void +ReferenceResidualConvergence::updateReferenceResidual() +{ + NonlinearSystemBase & _current_nl_sys = _fe_problem.currentNonlinearSystem(); + AuxiliarySystem & aux_sys = _fe_problem.getAuxiliarySystem(); + System & s = _current_nl_sys.system(); + auto & as = aux_sys.sys(); + + std::fill(_group_resid.begin(), _group_resid.end(), 0.0); + std::fill(_group_output_resid.begin(), _group_output_resid.end(), 0.0); + if (_local_norm) + std::fill(_group_ref_resid.begin(), _group_ref_resid.end(), 1.0); + else + std::fill(_group_ref_resid.begin(), _group_ref_resid.end(), 0.0); + + for (const auto i : index_range(_soln_vars)) + { + Real resid = 0.0; + const auto group = _variable_group_num_index[i]; + if (_local_norm) + { + mooseAssert(_current_nl_sys.RHS().size() == (*_reference_vector).size(), + "Sizes of nonlinear RHS and reference vector should be the same."); + mooseAssert((*_reference_vector).size(), "Reference vector must be provided."); + // Add a tiny number to the reference to prevent a divide by zero. + auto ref = _reference_vector->clone(); + ref->add(std::numeric_limits::min()); + auto div = _current_nl_sys.RHS().clone(); + *div /= *ref; + resid = Utility::pow<2>(s.calculate_norm(*div, _soln_vars[i], _norm_type)); + } + else + { + resid = Utility::pow<2>(s.calculate_norm(_current_nl_sys.RHS(), _soln_vars[i], _norm_type)); + if (_reference_vector) + { + const auto ref_resid = s.calculate_norm(*_reference_vector, _soln_vars[i], _norm_type); + _group_ref_resid[group] += Utility::pow<2>(ref_resid); + } + } + + _group_resid[group] += _converge_on_var[i] ? resid : 0; + _group_output_resid[group] += resid; + } + + if (!_reference_vector) + { + for (unsigned int i = 0; i < _ref_resid_vars.size(); ++i) + { + const auto ref_resid = + as.calculate_norm(*as.current_local_solution, _ref_resid_vars[i], _norm_type) * + _scaling_factors[i]; + _group_ref_resid[_variable_group_num_index[i]] += Utility::pow<2>(ref_resid); + } + } + + for (unsigned int i = 0; i < _group_resid.size(); ++i) + { + _group_resid[i] = std::sqrt(_group_resid[i]); + _group_output_resid[i] = std::sqrt(_group_output_resid[i]); + _group_ref_resid[i] = std::sqrt(_group_ref_resid[i]); + } +} + +void +ReferenceResidualConvergence::nonlinearConvergenceSetup() +{ + if (!_initialized) + initialSetup(); + + updateReferenceResidual(); + + std::ostringstream out; + + if (_group_soln_var_names.size() > 0) + { + out << std::setprecision(2) << std::scientific + << " Solution, reference convergence variable norms:\n"; + unsigned int maxwsv = 0; + unsigned int maxwrv = 0; + for (unsigned int i = 0; i < _group_soln_var_names.size(); ++i) + { + if (_group_soln_var_names[i].size() > maxwsv) + maxwsv = _group_soln_var_names[i].size(); + if (!_reference_vector && _group_ref_resid_var_names[i].size() > maxwrv) + maxwrv = _group_ref_resid_var_names[i].size(); + } + if (_reference_vector) + // maxwrv is the width of maxwsv plus the length of "_ref" (e.g. 4) + maxwrv = maxwsv + 4; + + for (unsigned int i = 0; i < _group_soln_var_names.size(); ++i) + { + out << " " << std::setw(maxwsv + (_local_norm ? 5 : 2)) << std::left + << (_local_norm ? "norm " : "") + _group_soln_var_names[i] + ": "; + + if (_group_output_resid[i] == _group_resid[i]) + out << std::setw(8) << _group_output_resid[i]; + else + out << std::setw(8) << _group_resid[i] << " (" << _group_output_resid[i] << ')'; + + if (!_local_norm) + { + const auto ref_var_name = + _reference_vector ? _group_soln_var_names[i] + "_ref" : _group_ref_resid_var_names[i]; + out << " " << std::setw(maxwrv + 2) << ref_var_name + ":" << std::setw(8) + << _group_ref_resid[i] << " (" << std::setw(8) + << (_group_ref_resid[i] ? _group_resid[i] / _group_ref_resid[i] : _group_resid[i]) + << ")"; + } + out << '\n'; + } + _console << out.str() << std::flush; + } +} + +bool +ReferenceResidualConvergence::checkConvergenceIndividVars( + const Real fnorm, + const Real abstol, + const Real rtol, + const Real initial_residual_before_preset_bcs) +{ + // Convergence is checked via: + // 1) if group residual is less than group reference residual by relative tolerance + // 2) if group residual is less than absolute tolerance + // 3) if group reference residual is zero and: + // 3.1) Convergence type is ZERO_TOLERANCE and group residual is zero (rare, but possible, and + // historically implemented that way) + // 3.2) Convergence type is RELATIVE_TOLERANCE and group residual + // is less than relative tolerance. (i.e., using the relative tolerance to check group + // convergence in an absolute way) + + bool convergedRelative = true; + if (_group_resid.size() > 0) + { + for (unsigned int i = 0; i < _group_resid.size(); ++i) + convergedRelative &= + (_group_resid[i] < _group_ref_resid[i] * rtol || _group_resid[i] < abstol || + (_group_ref_resid[i] == 0.0 && + ((_zero_ref_type == ZeroReferenceType::ZERO_TOLERANCE && _group_resid[i] == 0.0) || + (_zero_ref_type == ZeroReferenceType::RELATIVE_TOLERANCE && + _group_resid[i] <= rtol)))); + } + + else if (fnorm > initial_residual_before_preset_bcs * rtol) + convergedRelative = false; + + return convergedRelative; +} + +bool +ReferenceResidualConvergence::checkRelativeConvergence(const unsigned int it, + const Real fnorm, + const Real the_residual, + const Real rtol, + const Real abstol, + std::ostringstream & oss) +{ + if (checkConvergenceIndividVars(fnorm, abstol, rtol, the_residual)) + { + oss << "Converged due to function norm " << fnorm << " < relative tolerance (" << rtol + << ") or absolute tolerance (" << abstol << ") for all solution variables\n"; + return true; + } + else if (it >= _accept_iters && + checkConvergenceIndividVars( + fnorm, abstol * _accept_mult, rtol * _accept_mult, the_residual)) + { + oss << "Converged due to function norm " << fnorm << " < acceptable relative tolerance (" + << rtol * _accept_mult << ") or acceptable absolute tolerance (" << abstol * _accept_mult + << ") for all solution variables\n"; + _console << "Converged due to ACCEPTABLE tolerances" << std::endl; + return true; + } + + return false; +} diff --git a/framework/src/executioners/FEProblemSolve.C b/framework/src/executioners/FEProblemSolve.C index 964a28302d60..a0ca28d26ed5 100644 --- a/framework/src/executioners/FEProblemSolve.C +++ b/framework/src/executioners/FEProblemSolve.C @@ -11,6 +11,8 @@ #include "FEProblem.h" #include "NonlinearSystemBase.h" +#include "DefaultNonlinearConvergence.h" +#include "ReferenceResidualConvergence.h" std::set const FEProblemSolve::_moose_line_searches = {"contact", "project"}; @@ -20,10 +22,43 @@ FEProblemSolve::mooseLineSearches() return _moose_line_searches; } +InputParameters +FEProblemSolve::feProblemDefaultConvergenceParams() +{ + InputParameters params = emptyInputParameters(); + + params.addParam("nl_max_its", 50, "Max Nonlinear Iterations"); + params.addParam("nl_forced_its", 0, "The Number of Forced Nonlinear Iterations"); + params.addParam("nl_max_funcs", 10000, "Max Nonlinear solver function evaluations"); + params.addParam("nl_abs_tol", 1.0e-50, "Nonlinear Absolute Tolerance"); + params.addParam("nl_rel_tol", 1.0e-8, "Nonlinear Relative Tolerance"); + params.addParam( + "nl_div_tol", + 1.0e10, + "Nonlinear Relative Divergence Tolerance. A negative value disables this check."); + params.addParam( + "nl_abs_div_tol", + 1.0e50, + "Nonlinear Absolute Divergence Tolerance. A negative value disables this check."); + params.addParam("nl_rel_step_tol", 0., "Nonlinear Relative step Tolerance"); + params.addParam("n_max_nonlinear_pingpong", + 100, + "The maximum number of times the nonlinear residual can ping pong " + "before requesting halting the current evaluation and requesting " + "timestep cut for transient simulations"); + + params.addParamNamesToGroup("nl_max_its nl_forced_its nl_max_funcs nl_abs_tol nl_rel_tol " + "nl_rel_step_tol nl_div_tol nl_abs_div_tol n_max_nonlinear_pingpong", + "Nonlinear Solver"); + + return params; +} + InputParameters FEProblemSolve::validParams() { InputParameters params = emptyInputParameters(); + params += FEProblemSolve::feProblemDefaultConvergenceParams(); params.addParam>("splitting", {}, @@ -61,26 +96,11 @@ FEProblemSolve::validParams() params.addParam("l_tol", 1.0e-5, "Linear Relative Tolerance"); params.addParam("l_abs_tol", 1.0e-50, "Linear Absolute Tolerance"); params.addParam("l_max_its", 10000, "Max Linear Iterations"); - params.addParam("nl_max_its", 50, "Max Nonlinear Iterations"); - params.addParam("nl_forced_its", 0, "The Number of Forced Nonlinear Iterations"); - params.addParam("nl_max_funcs", 10000, "Max Nonlinear solver function evaluations"); - params.addParam("nl_abs_tol", 1.0e-50, "Nonlinear Absolute Tolerance"); - params.addParam("nl_rel_tol", 1.0e-8, "Nonlinear Relative Tolerance"); - params.addParam( - "nl_div_tol", - 1.0e10, - "Nonlinear Relative Divergence Tolerance. A negative value disables this check."); - params.addParam( - "nl_abs_div_tol", - 1.0e50, - "Nonlinear Absolute Divergence Tolerance. A negative value disables this check."); params.addParam("nl_abs_step_tol", 0., "Nonlinear Absolute step Tolerance"); - params.addParam("nl_rel_step_tol", 0., "Nonlinear Relative step Tolerance"); - params.addParam( - "n_max_nonlinear_pingpong", - 100, - "The maximum number of times the nonlinear residual can ping pong " - "before requesting halting the current evaluation and requesting timestep cut"); + params.addParam("nonlinear_convergence", + "Name of Convergence object to use to assess convergence of the " + "nonlinear solve. If not provided, the default Convergence " + "associated with the Problem will be constructed internally."); params.addParam( "snesmf_reuse_base", true, @@ -153,11 +173,9 @@ FEProblemSolve::validParams() params.addParamNamesToGroup("l_tol l_abs_tol l_max_its reuse_preconditioner " "reuse_preconditioner_max_linear_its", "Linear Solver"); - params.addParamNamesToGroup( - "solve_type nl_max_its nl_forced_its nl_max_funcs nl_abs_tol nl_rel_tol nl_abs_step_tol " - "nl_rel_step_tol snesmf_reuse_base use_pre_SMO_residual num_grids nl_div_tol nl_abs_div_tol " - "residual_and_jacobian_together n_max_nonlinear_pingpong splitting", - "Nonlinear Solver"); + params.addParamNamesToGroup("solve_type nl_abs_step_tol snesmf_reuse_base use_pre_SMO_residual " + "num_grids residual_and_jacobian_together splitting", + "Nonlinear Solver"); params.addParamNamesToGroup( "automatic_scaling compute_scaling_once off_diagonals_in_auto_scaling " "scaling_group_variables resid_vs_jac_scaling_param ignore_variables_for_autoscaling", @@ -190,26 +208,9 @@ FEProblemSolve::FEProblemSolve(Executioner & ex) es.parameters.set("linear solver maximum iterations") = getParam("l_max_its"); - es.parameters.set("nonlinear solver maximum iterations") = - getParam("nl_max_its"); - - es.parameters.set("nonlinear solver maximum function evaluations") = - getParam("nl_max_funcs"); - - es.parameters.set("nonlinear solver absolute residual tolerance") = - getParam("nl_abs_tol"); - - es.parameters.set("nonlinear solver relative residual tolerance") = - getParam("nl_rel_tol"); - - es.parameters.set("nonlinear solver divergence tolerance") = getParam("nl_div_tol"); - es.parameters.set("nonlinear solver absolute step tolerance") = getParam("nl_abs_step_tol"); - es.parameters.set("nonlinear solver relative step tolerance") = - getParam("nl_rel_step_tol"); - es.parameters.set("reuse preconditioner") = getParam("reuse_preconditioner"); es.parameters.set("reuse preconditioner maximum linear iterations") = @@ -220,11 +221,14 @@ FEProblemSolve::FEProblemSolve(Executioner & ex) _problem.skipExceptionCheck(getParam("skip_exception_check")); - _problem.setMaxNLPingPong(getParam("n_max_nonlinear_pingpong")); - - _problem.setNonlinearForcedIterations(getParam("nl_forced_its")); - - _problem.setNonlinearAbsoluteDivergenceTolerance(getParam("nl_abs_div_tol")); + if (isParamValid("nonlinear_convergence")) + { + _problem.setNonlinearConvergenceName(getParam("nonlinear_convergence")); + if (_problem.onlyAllowDefaultNonlinearConvergence()) + mooseError("The selected problem does not allow 'nonlinear_convergence' to be set."); + } + else + _problem.setNeedToAddDefaultNonlinearConvergence(); _nl.setDecomposition(_splitting); diff --git a/framework/src/interfaces/ReferenceResidualInterface.C b/framework/src/interfaces/ReferenceResidualInterface.C new file mode 100644 index 000000000000..58889f5a825c --- /dev/null +++ b/framework/src/interfaces/ReferenceResidualInterface.C @@ -0,0 +1,93 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#include "ReferenceResidualInterface.h" +#include "MooseObject.h" +#include "MooseEnum.h" + +InputParameters +ReferenceResidualInterface::validParams() +{ + InputParameters params = emptyInputParameters(); + + params.addParam>( + "solution_variables", "Set of solution variables to be checked for relative convergence"); + params.addParam>( + "reference_residual_variables", + "Set of variables that provide reference residuals for relative convergence check"); + params.addParam("reference_vector", "The tag name of the reference residual vector."); + params.addParam("acceptable_multiplier", + 1.0, + "Multiplier applied to relative tolerance for acceptable limit"); + params.addParam( + "acceptable_iterations", + 0, + "Iterations after which convergence to acceptable limits is accepted"); + params.addParam>>( + "group_variables", + "Name of variables that are grouped together to check convergence. (Multiple groups can be " + "provided, separated by semicolon)"); + params.addParam>( + "converge_on", + {}, + "If supplied, use only these variables in the individual variable convergence check"); + MooseEnum Lnorm("global_L2 local_L2 global_Linf local_Linf", "global_L2"); + params.addParam( + "normalization_type", + Lnorm, + "The normalization type used to compare the reference and actual residuals."); + Lnorm.addDocumentation("global_L2", + "Compare the L2 norm of the residual vector to the L2 norm of the " + "absolute reference vector to determine relative convergence"); + Lnorm.addDocumentation( + "local_L2", + "Compute the L2 norm of the residual vector divided component-wise by the absolute reference " + "vector to the L2 norm of the absolute reference vector to determine relative convergence"); + Lnorm.addDocumentation( + "global_Linf", + "Compare the L-infinity norm of the residual vector to the L-infinity norm of the " + "absolute reference vector to determine relative convergence"); + Lnorm.addDocumentation( + "local_Linf", + "Compute the L-infinity norm of the residual vector divided component-wise " + "by the absolute reference " + "vector to the L-infinity norm of the absolute reference vector to " + "determine relative convergence"); + + MooseEnum zero_ref_res("zero_tolerance relative_tolerance", "relative_tolerance"); + params.addParam("zero_reference_residual_treatment", + zero_ref_res, + "Determine behavior if a reference residual value of zero is present " + "for a particular variable."); + zero_ref_res.addDocumentation("zero_tolerance", + "Solve is treated as converged if the residual is zero"); + zero_ref_res.addDocumentation( + "relative_tolerance", + "Solve is treated as converged if the residual is below the relative tolerance"); + + params.addParamNamesToGroup("acceptable_iterations acceptable_multiplier", + "Acceptable convergence"); + params.addParamNamesToGroup("reference_vector reference_residual_variables", + "Reference residual"); + params.addParamNamesToGroup("solution_variables group_variables", + "Variables to check for convergence"); + + return params; +} + +ReferenceResidualInterface::ReferenceResidualInterface(const MooseObject * moose_object) + : _use_group_variables(false) +{ + if (moose_object->isParamValid("group_variables")) + { + _group_variables = + moose_object->getParam>>("group_variables"); + _use_group_variables = true; + } +} diff --git a/framework/src/interfaces/TaggingInterface.C b/framework/src/interfaces/TaggingInterface.C index 41165c45d252..93d57034a674 100644 --- a/framework/src/interfaces/TaggingInterface.C +++ b/framework/src/interfaces/TaggingInterface.C @@ -11,6 +11,7 @@ #include "Conversion.h" #include "FEProblem.h" #include "Assembly.h" +#include "ReferenceResidualConvergence.h" #include "ReferenceResidualProblem.h" #include "libmesh/dense_vector.h" @@ -127,25 +128,30 @@ TaggingInterface::TaggingInterface(const MooseObject * moose_object) const auto * const fe_problem = moose_object->parameters().getCheckedPointerParam("_fe_problem_base"); - if (const auto * const ref_problem = dynamic_cast(fe_problem)) + + for (const auto & conv : fe_problem->getConvergenceObjects()) { - const auto reference_tag = ref_problem->referenceVectorTagID({}); - auto create_tags_split = - [reference_tag](const auto & tags, auto & non_ref_tags, auto & ref_tags) + const auto * const ref_conv = dynamic_cast(conv.get()); + if (ref_conv) { - for (const auto tag : tags) - if (tag == reference_tag) - ref_tags.insert(tag); - else - non_ref_tags.insert(tag); - }; - create_tags_split(_vector_tags, _non_ref_vector_tags, _ref_vector_tags); - create_tags_split(_abs_vector_tags, _non_ref_abs_vector_tags, _ref_abs_vector_tags); - } - else - { - _non_ref_vector_tags = _vector_tags; - _non_ref_abs_vector_tags = _abs_vector_tags; + const auto reference_tag = ref_conv->referenceVectorTagID({}); + auto create_tags_split = + [reference_tag](const auto & tags, auto & non_ref_tags, auto & ref_tags) + { + for (const auto tag : tags) + if (tag == reference_tag) + ref_tags.insert(tag); + else + non_ref_tags.insert(tag); + }; + create_tags_split(_vector_tags, _non_ref_vector_tags, _ref_vector_tags); + create_tags_split(_abs_vector_tags, _non_ref_abs_vector_tags, _ref_abs_vector_tags); + } + else + { + _non_ref_vector_tags = _vector_tags; + _non_ref_abs_vector_tags = _abs_vector_tags; + } } } diff --git a/framework/src/parser/Builder.C b/framework/src/parser/Builder.C index 1a1a834fefec..753a4cb94f7d 100644 --- a/framework/src/parser/Builder.C +++ b/framework/src/parser/Builder.C @@ -1144,6 +1144,7 @@ Builder::extractParams(const std::string & prefix, InputParameters & p) setscalar(SolverVariableName, string); setscalar(AuxVariableName, string); setscalar(FunctionName, string); + setscalar(ConvergenceName, string); setscalar(MeshDivisionName, string); setscalar(UserObjectName, string); setscalar(VectorPostprocessorName, string); @@ -1220,6 +1221,7 @@ Builder::extractParams(const std::string & prefix, InputParameters & p) setvector(SolverVariableName, string); setvector(AuxVariableName, string); setvector(FunctionName, string); + setvector(ConvergenceName, string); setvector(MeshDivisionName, string); setvector(UserObjectName, string); setvector(IndicatorName, string); @@ -1285,6 +1287,7 @@ Builder::extractParams(const std::string & prefix, InputParameters & p) setvectorvector(SolverVariableName); setvectorvector(AuxVariableName); setvectorvector(FunctionName); + setvectorvector(ConvergenceName); setvectorvector(UserObjectName); setvectorvector(IndicatorName); setvectorvector(MarkerName); diff --git a/framework/src/postprocessors/PseudoTimestep.C b/framework/src/postprocessors/PseudoTimestep.C index 17502d07b3e0..fb2cdb09357a 100644 --- a/framework/src/postprocessors/PseudoTimestep.C +++ b/framework/src/postprocessors/PseudoTimestep.C @@ -160,7 +160,6 @@ PseudoTimestep::execute() { Transient * transient = dynamic_cast(_app.getExecutioner()); - // internal parameters needed for computing residuals and setting next timestep Real res_norm; Real curr_dt; Real update_dt; diff --git a/framework/src/problems/FEProblemBase.C b/framework/src/problems/FEProblemBase.C index ab2324027043..d7031b1783d8 100644 --- a/framework/src/problems/FEProblemBase.C +++ b/framework/src/problems/FEProblemBase.C @@ -35,6 +35,7 @@ #include "Parser.h" #include "ElementH1Error.h" #include "Function.h" +#include "Convergence.h" #include "NonlinearSystem.h" #include "LinearSystem.h" #include "SolverSystem.h" @@ -381,6 +382,8 @@ FEProblemBase::FEProblemBase(const InputParameters & parameters) _t_step(declareRecoverableData("t_step")), _dt(declareRestartableData("dt")), _dt_old(declareRestartableData("dt_old")), + _set_nonlinear_convergence_name(false), + _need_to_add_default_nonlinear_convergence(false), _linear_sys_names(getParam>("linear_sys_names")), _num_linear_sys(_linear_sys_names.size()), _linear_systems(_num_linear_sys, nullptr), @@ -974,6 +977,14 @@ FEProblemBase::initialSetup() unsigned int n_threads = libMesh::n_threads(); + // Convergence initial setup + { + TIME_SECTION("convergenceInitialSetup", 5, "Initializing Convergence objects"); + + for (THREAD_ID tid = 0; tid < n_threads; tid++) + _convergences.initialSetup(tid); + } + // UserObject initialSetup std::set depend_objects_ic = _ics.getDependObjects(); std::set depend_objects_aux = _aux->getDependObjects(); @@ -1022,7 +1033,7 @@ FEProblemBase::initialSetup() computeUserObjects(EXEC_INITIAL, Moose::PRE_IC); { - TIME_SECTION("ICiniitalSetup", 5, "Setting Up Initial Conditions"); + TIME_SECTION("ICinitialSetup", 5, "Setting Up Initial Conditions"); for (THREAD_ID tid = 0; tid < n_threads; tid++) _ics.initialSetup(tid); @@ -2383,6 +2394,31 @@ FEProblemBase::addFunction(const std::string & type, } } +void +FEProblemBase::addConvergence(const std::string & type, + const std::string & name, + InputParameters & parameters) +{ + parallel_object_only(); + + for (THREAD_ID tid = 0; tid < libMesh::n_threads(); tid++) + { + std::shared_ptr conv = _factory.create(type, name, parameters, tid); + _convergences.addObject(conv, tid); + } +} + +void +FEProblemBase::addDefaultNonlinearConvergence(const InputParameters & params_to_apply) +{ + const std::string class_name = "DefaultNonlinearConvergence"; + InputParameters params = _factory.getValidParams(class_name); + params.applyParameters(params_to_apply); + params.applyParameters(parameters()); + params.set("added_as_default") = true; + addConvergence(class_name, getNonlinearConvergenceName(), params); +} + bool FEProblemBase::hasFunction(const std::string & name, const THREAD_ID tid) { @@ -2434,6 +2470,28 @@ FEProblemBase::getFunction(const std::string & name, const THREAD_ID tid) return *ret; } +bool +FEProblemBase::hasConvergence(const std::string & name, const THREAD_ID tid) const +{ + return _convergences.hasActiveObject(name, tid); +} + +Convergence & +FEProblemBase::getConvergence(const std::string & name, const THREAD_ID tid) const +{ + auto * const ret = dynamic_cast(_convergences.getActiveObject(name, tid).get()); + if (!ret) + mooseError("The Convergence object '", name, "' does not exist."); + + return *ret; +} + +const std::vector> & +FEProblemBase::getConvergenceObjects(const THREAD_ID tid) const +{ + return _convergences.getActiveObjects(tid); +} + void FEProblemBase::addMeshDivision(const std::string & type, const std::string & name, @@ -8309,137 +8367,6 @@ FEProblemBase::getVariableNames() return names; } -MooseNonlinearConvergenceReason -FEProblemBase::checkNonlinearConvergence(std::string & msg, - const PetscInt it, - const Real xnorm, - const Real snorm, - const Real fnorm, - const Real rtol, - const Real divtol, - const Real stol, - const Real abstol, - const PetscInt nfuncs, - const PetscInt max_funcs, - const Real div_threshold) -{ - TIME_SECTION("checkNonlinearConvergence", 5, "Checking Nonlinear Convergence"); - mooseAssert(_current_nl_sys, "This should be non-null"); - - nonlinearConvergenceSetup(); - - if (_fail_next_nonlinear_convergence_check) - { - _fail_next_nonlinear_convergence_check = false; - return MooseNonlinearConvergenceReason::DIVERGED_FNORM_NAN; - } - - NonlinearSystemBase & system = *_current_nl_sys; - MooseNonlinearConvergenceReason reason = MooseNonlinearConvergenceReason::ITERATING; - - Real fnorm_old; - // This is the first residual before any iterations have been done, but after solution-modifying - // objects (if any) have been imposed on the solution vector. We save it, and use it to detect - // convergence if system.usePreSMOResidual() == false. - if (it == 0) - { - system.setInitialResidual(fnorm); - fnorm_old = fnorm; - _n_nl_pingpong = 0; - } - else - fnorm_old = system._last_nl_rnorm; - - // Check for nonlinear residual pingpong. - // Pingpong will always start from a residual increase - if ((_n_nl_pingpong % 2 == 1 && !(fnorm > fnorm_old)) || - (_n_nl_pingpong % 2 == 0 && fnorm > fnorm_old)) - _n_nl_pingpong += 1; - else - _n_nl_pingpong = 0; - - std::ostringstream oss; - if (fnorm != fnorm) - { - oss << "Failed to converge, function norm is NaN\n"; - reason = MooseNonlinearConvergenceReason::DIVERGED_FNORM_NAN; - } - else if ((it >= _nl_forced_its) && fnorm < abstol) - { - oss << "Converged due to function norm " << fnorm << " < " << abstol << '\n'; - reason = MooseNonlinearConvergenceReason::CONVERGED_FNORM_ABS; - } - else if (nfuncs >= max_funcs) - { - oss << "Exceeded maximum number of function evaluations: " << nfuncs << " > " << max_funcs - << '\n'; - reason = MooseNonlinearConvergenceReason::DIVERGED_FUNCTION_COUNT; - } - else if ((it >= _nl_forced_its) && it && fnorm > system._last_nl_rnorm && fnorm >= div_threshold) - { - oss << "Nonlinear solve was blowing up!\n"; - reason = MooseNonlinearConvergenceReason::DIVERGED_LINE_SEARCH; - } - - if ((it >= _nl_forced_its) && it && reason == MooseNonlinearConvergenceReason::ITERATING) - { - // Set the reference residual depending on what the user asks us to use. - const auto ref_residual = system.referenceResidual(); - if (checkRelativeConvergence(it, fnorm, ref_residual, rtol, abstol, oss)) - reason = MooseNonlinearConvergenceReason::CONVERGED_FNORM_RELATIVE; - else if (snorm < stol * xnorm) - { - oss << "Converged due to small update length: " << snorm << " < " << stol << " * " << xnorm - << '\n'; - reason = MooseNonlinearConvergenceReason::CONVERGED_SNORM_RELATIVE; - } - else if (divtol > 0 && fnorm > ref_residual * divtol) - { - oss << "Diverged due to residual " << fnorm << " > divergence tolerance " << divtol - << " * initial residual " << ref_residual << '\n'; - reason = MooseNonlinearConvergenceReason::DIVERGED_DTOL; - } - else if (_nl_abs_div_tol > 0 && fnorm > _nl_abs_div_tol) - { - oss << "Diverged due to residual " << fnorm << " > absolute divergence tolerance " - << _nl_abs_div_tol << '\n'; - reason = MooseNonlinearConvergenceReason::DIVERGED_DTOL; - } - else if (_n_nl_pingpong > _n_max_nl_pingpong) - { - oss << "Diverged due to maximum nonlinear residual pingpong achieved" << '\n'; - reason = MooseNonlinearConvergenceReason::DIVERGED_NL_RESIDUAL_PINGPONG; - } - } - - system._last_nl_rnorm = fnorm; - system._current_nl_its = static_cast(it); - - msg = oss.str(); - if (_app.multiAppLevel() > 0) - MooseUtils::indentMessage(_app.name(), msg); - - return reason; -} - -bool -FEProblemBase::checkRelativeConvergence(const PetscInt /*it*/, - const Real fnorm, - const Real ref_residual, - const Real rtol, - const Real /*abstol*/, - std::ostringstream & oss) -{ - if (_fail_next_nonlinear_convergence_check) - return false; - if (fnorm <= ref_residual * rtol) - { - oss << "Converged due to function norm " << fnorm << " < relative tolerance (" << rtol << ")\n"; - return true; - } - return false; -} - SolverParams & FEProblemBase::solverParams() { @@ -8837,6 +8764,15 @@ FEProblemBase::resizeMaterialData(const Moose::MaterialDataType data_type, getMaterialData(data_type, tid).resize(nqp); } +ConvergenceName +FEProblemBase::getNonlinearConvergenceName() const +{ + if (_set_nonlinear_convergence_name) + return _nonlinear_convergence_name; + else + mooseError("The nonlinear convergence name has not been set."); +} + void FEProblemBase::residualSetup() { diff --git a/framework/src/problems/ReferenceResidualProblem.C b/framework/src/problems/ReferenceResidualProblem.C index d5c6c36de28c..7a7bd40b58b6 100644 --- a/framework/src/problems/ReferenceResidualProblem.C +++ b/framework/src/problems/ReferenceResidualProblem.C @@ -7,19 +7,8 @@ //* Licensed under LGPL 2.1, please see LICENSE for details //* https://www.gnu.org/licenses/lgpl-2.1.html -// MOOSE includes #include "ReferenceResidualProblem.h" - -#include "AuxiliarySystem.h" -#include "MooseApp.h" -#include "MooseMesh.h" -#include "MooseVariable.h" -#include "MooseVariableScalar.h" -#include "NonlinearSystem.h" - -// libMesh includes -#include "libmesh/enum_norm_type.h" -#include "libmesh/utility.h" +#include "ReferenceResidualConvergence.h" registerMooseObject("MooseApp", ReferenceResidualProblem); @@ -27,544 +16,27 @@ InputParameters ReferenceResidualProblem::validParams() { InputParameters params = FEProblem::validParams(); + params += ReferenceResidualInterface::validParams(); + params.addClassDescription("Problem that checks for convergence relative to " "a user-supplied reference quantity rather than " "the initial residual"); - params.addParam>( - "solution_variables", "Set of solution variables to be checked for relative convergence"); - params.addParam>( - "reference_residual_variables", - "Set of variables that provide reference residuals for relative convergence check"); - params.addParam("reference_vector", "The tag name of the reference residual vector."); - params.addParam("acceptable_multiplier", - 1.0, - "Multiplier applied to relative tolerance for acceptable limit"); - params.addParam("acceptable_iterations", - 0, - "Iterations after which convergence to acceptable limits is accepted"); - params.addParam>>( - "group_variables", - "Name of variables that are grouped together to check convergence. (Multiple groups can be " - "provided, separated by semicolon)"); - params.addParam>( - "converge_on", - {}, - "If supplied, use only these variables in the individual variable convergence check"); - MooseEnum Lnorm("global_L2 local_L2 global_Linf local_Linf", "global_L2"); - params.addParam( - "normalization_type", - Lnorm, - "The normalization type used to compare the reference and actual residuals."); - Lnorm.addDocumentation("global_L2", - "Compare the L2 norm of the residual vector to the L2 norm of the " - "absolute reference vector to determine relative convergence"); - Lnorm.addDocumentation( - "local_L2", - "Compute the L2 norm of the residual vector divided componentwise by the absolute reference " - "vector to the L2 norm of the absolute reference vector to determine relative convergence"); - Lnorm.addDocumentation( - "global_Linf", - "Compare the L-infinity norm of the residual vector to the L-infinity norm of the " - "absolute reference vector to determine relative convergence"); - Lnorm.addDocumentation("local_Linf", - "Compute the L-infinity norm of the residual vector divided componentwise " - "by the absolute reference " - "vector to the L-infinity norm of the absolute reference vector to " - "determine relative convergence"); - MooseEnum zero_ref_res("zero_tolerance relative_tolerance", "relative_tolerance"); - params.addParam("zero_reference_residual_treatment", - zero_ref_res, - "Determine behavior if a reference residual value of zero is present " - "for a particular variable."); - zero_ref_res.addDocumentation("zero_tolerance", - "Solve is treated as converged if the residual is zero"); - zero_ref_res.addDocumentation( - "relative_tolerance", - "Solve is treated as converged if the residual is below the relative tolerance"); return params; } ReferenceResidualProblem::ReferenceResidualProblem(const InputParameters & params) - : FEProblem(params), - _use_group_variables(false), - _reference_vector(nullptr), - _converge_on(getParam>("converge_on")), - _zero_ref_type( - params.get("zero_reference_residual_treatment").getEnum()), - _reference_vector_tag_id(Moose::INVALID_TAG_ID) + : FEProblem(params), ReferenceResidualInterface(this) { - if (params.isParamValid("solution_variables")) - { - if (params.isParamValid("reference_vector")) - mooseDeprecated("The `solution_variables` parameter is deprecated, has no effect when " - "the tagging system is used, and will be removed on January 1, 2020. " - "Please simply delete this parameter from your input file."); - _soln_var_names = params.get>("solution_variables"); - } - - if (params.isParamValid("reference_residual_variables") && - params.isParamValid("reference_vector")) - mooseError( - "For `ReferenceResidualProblem` you can specify either the `reference_residual_variables` " - "or `reference_vector` parameter, not both. `reference_residual_variables` is deprecated " - "so we recommend using `reference_vector`"); - - if (params.isParamValid("reference_residual_variables")) - { - mooseDeprecated( - "The save-in method for composing reference residual quantities is deprecated " - "and will be removed on January 1, 2020. Please use the tagging system instead; " - "specifically, please assign a TagName to the `reference_vector` parameter"); - - _ref_resid_var_names = params.get>("reference_residual_variables"); - - if (_soln_var_names.size() != _ref_resid_var_names.size()) - mooseError("In ReferenceResidualProblem, size of solution_variables (", - _soln_var_names.size(), - ") != size of reference_residual_variables (", - _ref_resid_var_names.size(), - ")"); - } - else if (params.isParamValid("reference_vector")) - { - if (numNonlinearSystems() > 1) - paramError( - "nl_sys_names", - "reference residual problem does not currently support multiple nonlinear systems"); - _reference_vector_tag_id = getVectorTagID(getParam("reference_vector")); - _reference_vector = &getNonlinearSystemBase(0).getVector(_reference_vector_tag_id); - } - else - mooseInfo("Neither the `reference_residual_variables` nor `reference_vector` parameter is " - "specified for `ReferenceResidualProblem`, which means that no reference " - "quantites are set. Because of this, the standard technique of comparing the " - "norm of the full residual vector with its initial value will be used."); - - if (params.isParamValid("group_variables")) - { - _group_variables = - params.get>>("group_variables"); - _use_group_variables = true; - } - - _accept_mult = params.get("acceptable_multiplier"); - _accept_iters = params.get("acceptable_iterations"); - - const auto norm_type_enum = - params.get("normalization_type").getEnum(); - if (norm_type_enum == NormalizationType::LOCAL_L2) - { - _norm_type = DISCRETE_L2; - _local_norm = true; - } - else if (norm_type_enum == NormalizationType::GLOBAL_L2) - { - _norm_type = DISCRETE_L2; - _local_norm = false; - } - else if (norm_type_enum == NormalizationType::LOCAL_LINF) - { - _norm_type = DISCRETE_L_INF; - _local_norm = true; - } - else if (norm_type_enum == NormalizationType::GLOBAL_LINF) - { - _norm_type = DISCRETE_L_INF; - _local_norm = false; - } - else - mooseError("Internal error"); - - if (_local_norm && !params.isParamValid("reference_vector")) - paramError("reference_vector", "If local norm is used, a reference_vector must be provided."); } void -ReferenceResidualProblem::initialSetup() +ReferenceResidualProblem::addDefaultNonlinearConvergence(const InputParameters & params_to_apply) { - NonlinearSystemBase & nonlinear_sys = getNonlinearSystemBase(/*nl_sys=*/0); - AuxiliarySystem & aux_sys = getAuxiliarySystem(); - System & s = nonlinear_sys.system(); - auto & as = aux_sys.sys(); - - if (_soln_var_names.empty()) - { - // If the user provides reference_vector, that implies that they want the - // individual variables compared against their reference quantities in the - // tag vector. The code depends on having _soln_var_names populated, - // so fill that out if they didn't specify solution_variables. - if (_reference_vector) - for (unsigned int var_num = 0; var_num < s.n_vars(); var_num++) - _soln_var_names.push_back(s.variable_name(var_num)); - - // If they didn't provide reference_vector, that implies that they - // want to skip the individual variable comparison, so leave it alone. - } - else if (_soln_var_names.size() != s.n_vars()) - mooseError("In ReferenceResidualProblem, size of solution_variables (", - _soln_var_names.size(), - ") != number of variables in system (", - s.n_vars(), - ")"); - - const auto n_soln_vars = _soln_var_names.size(); - _variable_group_num_index.resize(n_soln_vars); - - if (!_converge_on.empty()) - { - _converge_on_var.assign(n_soln_vars, false); - for (std::size_t i = 0; i < n_soln_vars; ++i) - for (const auto & c : _converge_on) - if (MooseUtils::globCompare(_soln_var_names[i], c)) - { - _converge_on_var[i] = true; - break; - } - } - else - _converge_on_var.assign(n_soln_vars, true); - - unsigned int group_variable_num = 0; - if (_use_group_variables) - { - for (unsigned int i = 0; i < _group_variables.size(); ++i) - { - group_variable_num += _group_variables[i].size(); - if (_group_variables[i].size() == 1) - mooseError(" In the 'group_variables' parameter, variable ", - _group_variables[i][0], - " is not grouped with other variables."); - } - - unsigned int size = n_soln_vars - group_variable_num + _group_variables.size(); - _group_ref_resid.resize(size); - _group_resid.resize(size); - _group_output_resid.resize(size); - _group_soln_var_names.resize(size); - _group_ref_resid_var_names.resize(size); - } - else - { - _group_ref_resid.resize(n_soln_vars); - _group_resid.resize(n_soln_vars); - _group_output_resid.resize(n_soln_vars); - _group_soln_var_names.resize(n_soln_vars); - _group_ref_resid_var_names.resize(n_soln_vars); - } - - std::set check_duplicate; - if (_use_group_variables) - { - for (unsigned int i = 0; i < _group_variables.size(); ++i) - for (unsigned int j = 0; j < _group_variables[i].size(); ++j) - check_duplicate.insert(_group_variables[i][j]); - - if (check_duplicate.size() != group_variable_num) - mooseError( - "A variable cannot be included in multiple groups in the 'group_variables' parameter."); - } - - _soln_vars.clear(); - for (unsigned int i = 0; i < n_soln_vars; ++i) - { - bool foundMatch = false; - for (unsigned int var_num = 0; var_num < s.n_vars(); var_num++) - if (_soln_var_names[i] == s.variable_name(var_num)) - { - _soln_vars.push_back(var_num); - foundMatch = true; - break; - } - - if (!foundMatch) - mooseError("Could not find solution variable '", _soln_var_names[i], "' in system"); - } - - if (!_reference_vector) - { - _ref_resid_vars.clear(); - for (unsigned int i = 0; i < _ref_resid_var_names.size(); ++i) - { - bool foundMatch = false; - for (unsigned int var_num = 0; var_num < as.n_vars(); var_num++) - if (_ref_resid_var_names[i] == as.variable_name(var_num)) - { - _ref_resid_vars.push_back(var_num); - foundMatch = true; - break; - } - - if (!foundMatch) - mooseError("Could not find variable '", _ref_resid_var_names[i], "' in auxiliary system"); - } - } - - unsigned int ungroup_index = 0; - if (_use_group_variables) - ungroup_index = _group_variables.size(); - - for (unsigned int i = 0; i < _soln_vars.size(); ++i) - { - bool find_group = false; - if (_use_group_variables) - { - for (unsigned int j = 0; j < _group_variables.size(); ++j) - if (std::find(_group_variables[j].begin(), - _group_variables[j].end(), - s.variable_name(_soln_vars[i])) != _group_variables[j].end()) - { - if (!_converge_on_var[i]) - paramError("converge_on", - "You added variable '", - _soln_var_names[i], - "' to a group but excluded it from the convergence check. This is not " - "permitted."); - - _variable_group_num_index[i] = j; - find_group = true; - break; - } - - if (!find_group) - { - _variable_group_num_index[i] = ungroup_index; - ungroup_index++; - } - } - else - _variable_group_num_index[i] = i; - } - - if (_use_group_variables) - { - for (unsigned int i = 0; i < _group_variables.size(); ++i) - { - unsigned int num_scalar_vars = 0; - unsigned int num_field_vars = 0; - if (_group_variables[i].size() > 1) - { - for (unsigned int j = 0; j < _group_variables[i].size(); ++j) - for (unsigned int var_num = 0; var_num < s.n_vars(); var_num++) - if (_group_variables[i][j] == s.variable_name(var_num)) - { - if (nonlinear_sys.isScalarVariable(_soln_vars[var_num])) - ++num_scalar_vars; - else - ++num_field_vars; - break; - } - } - if (num_scalar_vars > 0 && num_field_vars > 0) - mooseWarning("In the 'group_variables' parameter, standard variables and scalar variables " - "are grouped together in group ", - i); - } - } - - for (unsigned int i = 0; i < n_soln_vars; ++i) - { - if (_group_soln_var_names[_variable_group_num_index[i]].empty()) - { - _group_soln_var_names[_variable_group_num_index[i]] = _soln_var_names[i]; - if (_use_group_variables && _variable_group_num_index[i] < _group_variables.size()) - _group_soln_var_names[_variable_group_num_index[i]] += " (grouped) "; - } - - if (!_reference_vector && _group_ref_resid_var_names[_variable_group_num_index[i]].empty()) - { - _group_ref_resid_var_names[_variable_group_num_index[i]] = _ref_resid_var_names[i]; - if (_use_group_variables && _variable_group_num_index[i] < _group_variables.size()) - _group_ref_resid_var_names[_variable_group_num_index[i]] += " (grouped) "; - } - } - - if (!_reference_vector) - { - const unsigned int size_solnVars = _soln_vars.size(); - _scaling_factors.resize(size_solnVars); - for (unsigned int i = 0; i < size_solnVars; ++i) - if (nonlinear_sys.isScalarVariable(_soln_vars[i])) - _scaling_factors[i] = nonlinear_sys.getScalarVariable(0, _soln_vars[i]).scalingFactor(); - else - _scaling_factors[i] = nonlinear_sys.getVariable(/*tid*/ 0, _soln_vars[i]).scalingFactor(); - } - - FEProblemBase::initialSetup(); -} - -void -ReferenceResidualProblem::updateReferenceResidual() -{ - mooseAssert(_current_nl_sys, "This should be non-null"); - AuxiliarySystem & aux_sys = getAuxiliarySystem(); - System & s = _current_nl_sys->system(); - auto & as = aux_sys.sys(); - - std::fill(_group_resid.begin(), _group_resid.end(), 0.0); - std::fill(_group_output_resid.begin(), _group_output_resid.end(), 0.0); - if (_local_norm) - std::fill(_group_ref_resid.begin(), _group_ref_resid.end(), 1.0); - else - std::fill(_group_ref_resid.begin(), _group_ref_resid.end(), 0.0); - - for (unsigned int i = 0; i < _soln_vars.size(); ++i) - { - Real resid = 0.0; - const auto group = _variable_group_num_index[i]; - if (_local_norm) - { - mooseAssert(_current_nl_sys->RHS().size() == (*_reference_vector).size(), - "Sizes of nonlinear RHS and reference vector should be the same."); - mooseAssert((*_reference_vector).size(), "Reference vector must be provided."); - // Add a tiny number to the reference to prevent a divide by zero. - auto ref = _reference_vector->clone(); - ref->add(std::numeric_limits::min()); - auto div = _current_nl_sys->RHS().clone(); - *div /= *ref; - resid = Utility::pow<2>(s.calculate_norm(*div, _soln_vars[i], _norm_type)); - } - else - { - resid = Utility::pow<2>(s.calculate_norm(_current_nl_sys->RHS(), _soln_vars[i], _norm_type)); - if (_reference_vector) - { - const auto ref_resid = s.calculate_norm(*_reference_vector, _soln_vars[i], _norm_type); - _group_ref_resid[group] += Utility::pow<2>(ref_resid); - } - } - - _group_resid[group] += _converge_on_var[i] ? resid : 0; - _group_output_resid[group] += resid; - } - - if (!_reference_vector) - { - for (unsigned int i = 0; i < _ref_resid_vars.size(); ++i) - { - const auto ref_resid = - as.calculate_norm(*as.current_local_solution, _ref_resid_vars[i], _norm_type) * - _scaling_factors[i]; - _group_ref_resid[_variable_group_num_index[i]] += Utility::pow<2>(ref_resid); - } - } - - for (unsigned int i = 0; i < _group_resid.size(); ++i) - { - _group_resid[i] = std::sqrt(_group_resid[i]); - _group_output_resid[i] = std::sqrt(_group_output_resid[i]); - _group_ref_resid[i] = std::sqrt(_group_ref_resid[i]); - } -} - -void -ReferenceResidualProblem::nonlinearConvergenceSetup() -{ - updateReferenceResidual(); - - std::ostringstream out; - - if (_group_soln_var_names.size() > 0) - { - out << std::setprecision(2) << std::scientific - << " Solution, reference convergence variable norms:\n"; - unsigned int maxwsv = 0; - unsigned int maxwrv = 0; - for (unsigned int i = 0; i < _group_soln_var_names.size(); ++i) - { - if (_group_soln_var_names[i].size() > maxwsv) - maxwsv = _group_soln_var_names[i].size(); - if (!_reference_vector && _group_ref_resid_var_names[i].size() > maxwrv) - maxwrv = _group_ref_resid_var_names[i].size(); - } - if (_reference_vector) - // maxwrv is the width of maxwsv plus the length of "_ref" (e.g. 4) - maxwrv = maxwsv + 4; - - for (unsigned int i = 0; i < _group_soln_var_names.size(); ++i) - { - out << " " << std::setw(maxwsv + (_local_norm ? 5 : 2)) << std::left - << (_local_norm ? "norm " : "") + _group_soln_var_names[i] + ": "; - - if (_group_output_resid[i] == _group_resid[i]) - out << std::setw(8) << _group_output_resid[i]; - else - out << std::setw(8) << _group_resid[i] << " (" << _group_output_resid[i] << ')'; - - if (!_local_norm) - { - const auto ref_var_name = - _reference_vector ? _group_soln_var_names[i] + "_ref" : _group_ref_resid_var_names[i]; - out << " " << std::setw(maxwrv + 2) << ref_var_name + ":" << std::setw(8) - << _group_ref_resid[i] << " (" << std::setw(8) - << (_group_ref_resid[i] ? _group_resid[i] / _group_ref_resid[i] : _group_resid[i]) - << ")"; - } - out << '\n'; - } - - _console << out.str() << std::flush; - } -} - -bool -ReferenceResidualProblem::checkRelativeConvergence(const PetscInt it, - const Real fnorm, - const Real the_residual, - const Real rtol, - const Real abstol, - std::ostringstream & oss) -{ - if (checkConvergenceIndividVars(fnorm, abstol, rtol, the_residual)) - { - oss << "Converged due to function norm " << fnorm << " < relative tolerance (" << rtol - << ") or absolute tolerance (" << abstol << ") for all solution variables\n"; - return true; - } - else if (it >= _accept_iters && - checkConvergenceIndividVars( - fnorm, abstol * _accept_mult, rtol * _accept_mult, the_residual)) - { - oss << "Converged due to function norm " << fnorm << " < acceptable relative tolerance (" - << rtol * _accept_mult << ") or acceptable absolute tolerance (" << abstol * _accept_mult - << ") for all solution variables\n"; - _console << "Converged due to ACCEPTABLE tolerances" << std::endl; - return true; - } - - return false; -} - -bool -ReferenceResidualProblem::checkConvergenceIndividVars(const Real fnorm, - const Real abstol, - const Real rtol, - const Real initial_residual_before_preset_bcs) -{ - // Convergence is checked via: - // 1) if group residual is less than group reference residual by relative tolerance - // 2) if group residual is less than absolute tolerance - // 3) if group reference residual is zero and: - // 3.1) Convergence type is ZERO_TOLERANCE and group residual is zero (rare, but possible, and - // historically implemented way) - // 3.2) Convergence type is RELATIVE_TOLERANCE and group residual - // is less than relative tolerance. (i.e., using the relative tolerance to check group - // convergence in an absolute way) - - bool convergedRelative = true; - if (_group_resid.size() > 0) - { - for (unsigned int i = 0; i < _group_resid.size(); ++i) - convergedRelative &= - (_group_resid[i] < _group_ref_resid[i] * rtol || _group_resid[i] < abstol || - (_group_ref_resid[i] == 0.0 && - ((_zero_ref_type == ZeroReferenceType::ZERO_TOLERANCE && _group_resid[i] == 0.0) || - (_zero_ref_type == ZeroReferenceType::RELATIVE_TOLERANCE && - _group_resid[i] <= rtol)))); - } - - else if (fnorm > initial_residual_before_preset_bcs * rtol) - convergedRelative = false; - - return convergedRelative; + const std::string class_name = "ReferenceResidualConvergence"; + InputParameters params = _factory.getValidParams(class_name); + params.applyParameters(params_to_apply); + params.applyParameters(parameters()); + params.set("added_as_default") = true; + addConvergence(class_name, getNonlinearConvergenceName(), params); } diff --git a/framework/src/utils/PetscSupport.C b/framework/src/utils/PetscSupport.C index b3e89e52f52c..23dcf7d568e9 100644 --- a/framework/src/utils/PetscSupport.C +++ b/framework/src/utils/PetscSupport.C @@ -27,6 +27,7 @@ #include "Executioner.h" #include "MooseMesh.h" #include "ComputeLineSearchObjectWrapper.h" +#include "Convergence.h" #include "libmesh/equation_systems.h" #include "libmesh/linear_implicit_system.h" @@ -286,11 +287,11 @@ petscSetupOutput(CommandLine * cmd_line) } PetscErrorCode -petscNonlinearConverged(SNES snes, +petscNonlinearConverged(SNES /*snes*/, PetscInt it, - PetscReal xnorm, - PetscReal snorm, - PetscReal fnorm, + PetscReal /*xnorm*/, + PetscReal /*snorm*/, + PetscReal /*fnorm*/, SNESConvergedReason * reason, void * ctx) { @@ -300,129 +301,32 @@ petscNonlinearConverged(SNES snes, // execute objects that may be used in convergence check problem.execute(EXEC_NONLINEAR_CONVERGENCE); - // Let's be nice and always check PETSc error codes. - auto ierr = (PetscErrorCode)0; - - // Temporary variables to store SNES tolerances. Usual C-style would be to declare - // but not initialize these... but it bothers me to leave anything uninitialized. - PetscReal atol = 0.; // absolute convergence tolerance - PetscReal rtol = 0.; // relative convergence tolerance - PetscReal stol = 0.; // convergence (step) tolerance in terms of the norm of the change in the - // solution between steps - PetscInt maxit = 0; // maximum number of iterations - PetscInt maxf = 0; // maximum number of function evaluations - - // Ask the SNES object about its tolerances. - ierr = SNESGetTolerances(snes, &atol, &rtol, &stol, &maxit, &maxf); - CHKERRABORT(problem.comm().get(), ierr); - - // Ask the SNES object about its divergence tolerance. - PetscReal divtol = 0.; // relative divergence tolerance -#if !PETSC_VERSION_LESS_THAN(3, 8, 0) - ierr = SNESGetDivergenceTolerance(snes, &divtol); - CHKERRABORT(problem.comm().get(), ierr); -#endif - - // Get current number of function evaluations done by SNES. - PetscInt nfuncs = 0; - ierr = SNESGetNumberFunctionEvals(snes, &nfuncs); - CHKERRABORT(problem.comm().get(), ierr); - - // Whether or not to force SNESSolve() take at least one iteration regardless of the initial - // residual norm -#if !PETSC_VERSION_LESS_THAN(3, 8, 4) - PetscBool force_iteration = PETSC_FALSE; - ierr = SNESGetForceIteration(snes, &force_iteration); - CHKERRABORT(problem.comm().get(), ierr); - - if (force_iteration && !(problem.getNonlinearForcedIterations())) - problem.setNonlinearForcedIterations(1); - - if (!force_iteration && (problem.getNonlinearForcedIterations())) + // perform the convergence check + Convergence::MooseConvergenceStatus status; + if (problem.getFailNextNonlinearConvergenceCheck()) { - ierr = SNESSetForceIteration(snes, PETSC_TRUE); - CHKERRABORT(problem.comm().get(), ierr); + status = Convergence::MooseConvergenceStatus::DIVERGED; + problem.resetFailNextNonlinearConvergenceCheck(); } -#endif - - // See if SNESSetFunctionDomainError() has been called. Note: - // SNESSetFunctionDomainError() and SNESGetFunctionDomainError() - // were added in different releases of PETSc. - PetscBool domainerror; - ierr = SNESGetFunctionDomainError(snes, &domainerror); - CHKERRABORT(problem.comm().get(), ierr); - if (domainerror) + else { - *reason = SNES_DIVERGED_FUNCTION_DOMAIN; - PetscFunctionReturn(PETSC_SUCCESS); + auto & convergence = problem.getConvergence(problem.getNonlinearConvergenceName()); + status = convergence.checkConvergence(it); } - // Error message that will be set by the FEProblemBase. - std::string msg; - - // xnorm: 2-norm of current iterate - // snorm: 2-norm of current step - // fnorm: 2-norm of function at current iterate - MooseNonlinearConvergenceReason moose_reason = - problem.checkNonlinearConvergence(msg, - it, - xnorm, - snorm, - fnorm, - rtol, - divtol, - stol, - atol, - nfuncs, - maxf, - std::numeric_limits::max()); - - if (msg.length() > 0) -#if !PETSC_VERSION_LESS_THAN(3, 17, 0) - ierr = PetscInfo(snes, "%s", msg.c_str()); -#else - ierr = PetscInfo(snes, msg.c_str()); -#endif - CHKERRABORT(problem.comm().get(), ierr); - - switch (moose_reason) + // convert convergence status to PETSc converged reason + switch (status) { - case MooseNonlinearConvergenceReason::ITERATING: + case Convergence::MooseConvergenceStatus::ITERATING: *reason = SNES_CONVERGED_ITERATING; break; - case MooseNonlinearConvergenceReason::CONVERGED_FNORM_ABS: + case Convergence::MooseConvergenceStatus::CONVERGED: *reason = SNES_CONVERGED_FNORM_ABS; break; - case MooseNonlinearConvergenceReason::CONVERGED_FNORM_RELATIVE: - *reason = SNES_CONVERGED_FNORM_RELATIVE; - break; - - case MooseNonlinearConvergenceReason::DIVERGED_DTOL: -#if !PETSC_VERSION_LESS_THAN(3, 8, 0) // A new convergence enum in PETSc 3.8 + case Convergence::MooseConvergenceStatus::DIVERGED: *reason = SNES_DIVERGED_DTOL; -#endif - break; - - case MooseNonlinearConvergenceReason::CONVERGED_SNORM_RELATIVE: - *reason = SNES_CONVERGED_SNORM_RELATIVE; - break; - - case MooseNonlinearConvergenceReason::DIVERGED_FUNCTION_COUNT: - *reason = SNES_DIVERGED_FUNCTION_COUNT; - break; - - case MooseNonlinearConvergenceReason::DIVERGED_FNORM_NAN: - *reason = SNES_DIVERGED_FNORM_NAN; - break; - - case MooseNonlinearConvergenceReason::DIVERGED_LINE_SEARCH: - *reason = SNES_DIVERGED_LINE_SEARCH; - break; - - case MooseNonlinearConvergenceReason::DIVERGED_NL_RESIDUAL_PINGPONG: - *reason = SNES_DIVERGED_LOCAL_MIN; break; } @@ -553,7 +457,8 @@ petscSetDefaults(FEProblemBase & problem) // we use the default context provided by PETSc in addition to // a few other tests. { - ierr = SNESSetConvergenceTest(snes, petscNonlinearConverged, &problem, LIBMESH_PETSC_NULLPTR); + auto ierr = + SNESSetConvergenceTest(snes, petscNonlinearConverged, &problem, LIBMESH_PETSC_NULLPTR); CHKERRABORT(nl.comm().get(), ierr); } diff --git a/modules/contact/doc/content/source/convergence/AugmentedLagrangianContactFEProblemConvergence.md b/modules/contact/doc/content/source/convergence/AugmentedLagrangianContactFEProblemConvergence.md new file mode 100644 index 000000000000..7be2a3482406 --- /dev/null +++ b/modules/contact/doc/content/source/convergence/AugmentedLagrangianContactFEProblemConvergence.md @@ -0,0 +1,13 @@ +# AugmentedLagrangianContactFEProblemConvergence + +!syntax description /Convergence/AugmentedLagrangianContactFEProblemConvergence + +The augmented Lagrangian contact algorithm involves a nested solution strategy; see [AugmentedLagrangianContactProblem.md]. + +The `AugmentedLagrangianContactFEProblemConvergence` object tracks the convergence of the nested solution procedure described above, allows interaction with the solver at each iteration until the tolerance criteria are met, and allows for updating the Lagrangian multipliers as prescribed by [AugmentedLagrangianContactProblem.md] + +!syntax parameters /Convergence/AugmentedLagrangianContactFEProblemConvergence + +!syntax inputs /Convergence/AugmentedLagrangianContactFEProblemConvergence + +!syntax children /Convergence/AugmentedLagrangianContactFEProblemConvergence diff --git a/modules/contact/doc/content/source/problems/AugmentedLagrangianContactProblem.md b/modules/contact/doc/content/source/problems/AugmentedLagrangianContactProblem.md index 069e27f311a1..b4eec50913f1 100644 --- a/modules/contact/doc/content/source/problems/AugmentedLagrangianContactProblem.md +++ b/modules/contact/doc/content/source/problems/AugmentedLagrangianContactProblem.md @@ -1,8 +1,8 @@ # AugmentedLagrangianContactProblem -The augmented Lagrangian contact algorithm involves a nested solution strategy. In the inner solve, the Lagrangian multipliers are kept fixed and the contact problem is solved. Once the problem is solved, the algorithm checks to see whether the constraints are satisfied and decides if convergence has been reached. If the model has not yet converged, the Lagrangian multipliers are updated and the inner solve is repeated. +The augmented Lagrangian contact algorithm involves a nested solution strategy. In the inner solve, the Lagrangian multipliers are kept fixed and the contact problem is solved. Once the problem is solved, the algorithm checks to see whether the constraints are satisfied and decides if convergence has been reached. If the model has not yet converged, the Lagrangian multipliers are updated, and the inner solve is repeated. -The AugmentedLagrangianContactProblem manages the nested solution procedure described above, repeating the solution until convergence has been achieved, checking for convergence, and updating the Lagrangian multipliers. +The AugmentedLagrangianContactProblem manages the nested solution procedure described above, repeating the solution until convergence has been achieved, which is controlled by [AugmentedLagrangianContactFEProblemConvergence.md], and updating the Lagrangian multipliers. # AugmentedLagrangianContactProblem diff --git a/modules/contact/include/convergence/AugmentedLagrangianContactConvergence.h b/modules/contact/include/convergence/AugmentedLagrangianContactConvergence.h new file mode 100644 index 000000000000..2310c6ffaf29 --- /dev/null +++ b/modules/contact/include/convergence/AugmentedLagrangianContactConvergence.h @@ -0,0 +1,39 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "AugmentedLagrangianContactProblemInterface.h" +#include "ReferenceResidualConvergence.h" +#include "DefaultNonlinearConvergence.h" + +/** + * Class to check convergence for the augmented Lagrangian contact problem. + * @tparam T a convergence class type to use for such problems + */ +template +class AugmentedLagrangianContactConvergence : public T, + public AugmentedLagrangianContactProblemInterface +{ +public: + static InputParameters validParams(); + + AugmentedLagrangianContactConvergence(const InputParameters & params); + + virtual Convergence::MooseConvergenceStatus checkConvergence(unsigned int iter) override; + +protected: + using AugmentedLagrangianContactProblemInterface::_lagrangian_iteration_number; + using AugmentedLagrangianContactProblemInterface::_maximum_number_lagrangian_iterations; +}; + +typedef AugmentedLagrangianContactConvergence + AugmentedLagrangianContactReferenceConvergence; +typedef AugmentedLagrangianContactConvergence + AugmentedLagrangianContactFEProblemConvergence; diff --git a/modules/contact/include/problems/AugmentedLagrangianContactProblem.h b/modules/contact/include/problems/AugmentedLagrangianContactProblem.h index d1261a4409a5..ac7b1236bf48 100644 --- a/modules/contact/include/problems/AugmentedLagrangianContactProblem.h +++ b/modules/contact/include/problems/AugmentedLagrangianContactProblem.h @@ -13,24 +13,7 @@ #include "FEProblem.h" #include "NodeFaceConstraint.h" #include "MechanicalContactConstraint.h" - -class AugmentedLagrangianContactProblemInterface -{ -public: - static InputParameters validParams(); - AugmentedLagrangianContactProblemInterface(const InputParameters & params); - virtual const unsigned int & getLagrangianIterationNumber() const - { - return _lagrangian_iteration_number; - } - -protected: - /// maximum mumber of augmented lagrange iterations - const unsigned int _maximum_number_lagrangian_iterations; - - /// current augmented lagrange iteration number - unsigned int _lagrangian_iteration_number; -}; +#include "AugmentedLagrangianContactProblemInterface.h" /** * Class to manage nested solution for augmented Lagrange contact. @@ -49,29 +32,12 @@ class AugmentedLagrangianContactProblemTempl : public T, virtual ~AugmentedLagrangianContactProblemTempl() {} virtual void timestepSetup() override; - - virtual MooseNonlinearConvergenceReason - checkNonlinearConvergence(std::string & msg, - const PetscInt it, - const Real xnorm, - const Real snorm, - const Real fnorm, - const Real rtol, - const Real divtol, - const Real stol, - const Real abstol, - const PetscInt nfuncs, - const PetscInt max_funcs, - const Real div_threshold) override; + virtual void addDefaultNonlinearConvergence(const InputParameters & params) override; + virtual bool onlyAllowDefaultNonlinearConvergence() const override { return true; } protected: using AugmentedLagrangianContactProblemInterface::_lagrangian_iteration_number; using AugmentedLagrangianContactProblemInterface::_maximum_number_lagrangian_iterations; - using FEProblem::_console; - using FEProblem::currentNonlinearSystem; - using FEProblem::geomSearchData; - using FEProblem::getDisplacedProblem; - using FEProblem::theWarehouse; }; typedef AugmentedLagrangianContactProblemTempl diff --git a/modules/contact/include/problems/AugmentedLagrangianContactProblemInterface.h b/modules/contact/include/problems/AugmentedLagrangianContactProblemInterface.h new file mode 100644 index 000000000000..bacbefadda3c --- /dev/null +++ b/modules/contact/include/problems/AugmentedLagrangianContactProblemInterface.h @@ -0,0 +1,38 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "InputParameters.h" + +/** + * Class to provide an interface for parameters and routines required to check + * convergence for the augmented Lagrangian contact problem. + */ +class AugmentedLagrangianContactProblemInterface +{ +public: + static InputParameters validParams(); + AugmentedLagrangianContactProblemInterface(const InputParameters & params); + virtual const unsigned int & getLagrangianIterationNumber() const + { + return _lagrangian_iteration_number; + } + virtual void setLagrangianIterationNumber(unsigned int iter) + { + _lagrangian_iteration_number = iter; + } + +protected: + /// maximum mumber of augmented lagrange iterations + const unsigned int _maximum_number_lagrangian_iterations; + + /// current augmented lagrange iteration number + unsigned int _lagrangian_iteration_number; +}; diff --git a/modules/contact/src/convergence/AugmentedLagrangianContactConvergence.C b/modules/contact/src/convergence/AugmentedLagrangianContactConvergence.C new file mode 100644 index 000000000000..631d523c03a1 --- /dev/null +++ b/modules/contact/src/convergence/AugmentedLagrangianContactConvergence.C @@ -0,0 +1,176 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#include "AugmentedLagrangianContactConvergence.h" + +// MOOSE includes +#include "AuxiliarySystem.h" +#include "DisplacedProblem.h" +#include "MooseApp.h" +#include "MooseMesh.h" +#include "MooseVariable.h" +#include "NearestNodeLocator.h" +#include "NonlinearSystem.h" +#include "PenetrationLocator.h" +#include "FEProblemBase.h" +#include "SystemBase.h" +#include "Assembly.h" +#include "Executioner.h" +#include "AddVariableAction.h" +#include "ConstraintWarehouse.h" +#include "MortarUserObject.h" +#include "AugmentedLagrangeInterface.h" +#include "DefaultNonlinearConvergence.h" +#include "ReferenceResidualConvergence.h" +#include "MechanicalContactConstraint.h" + +registerMooseObject("ContactApp", AugmentedLagrangianContactReferenceConvergence); +registerMooseObject("ContactApp", AugmentedLagrangianContactFEProblemConvergence); + +template +InputParameters +AugmentedLagrangianContactConvergence::validParams() +{ + InputParameters params = T::validParams(); + params += AugmentedLagrangianContactProblemInterface::validParams(); + params.addClassDescription("Convergence for augmented Lagrangian contact"); + return params; +} + +template +AugmentedLagrangianContactConvergence::AugmentedLagrangianContactConvergence( + const InputParameters & params) + : T(params), AugmentedLagrangianContactProblemInterface(params) +{ +} + +template +Convergence::MooseConvergenceStatus +AugmentedLagrangianContactConvergence::checkConvergence(unsigned int iter) +{ + // Check convergence of the nonlinear problem + auto reason = T::checkConvergence(iter); + + auto & fe_problem_base = this->getMooseApp().feProblem(); + + auto aug_contact = dynamic_cast(&fe_problem_base); + _lagrangian_iteration_number = aug_contact->getLagrangianIterationNumber(); + + bool repeat_augmented_lagrange_step = false; + + // Nonlinear solve is converged, now check that the constraints are met + if (reason == Convergence::MooseConvergenceStatus::CONVERGED) + { + if (_lagrangian_iteration_number < _maximum_number_lagrangian_iterations) + { + + auto & nonlinear_sys = fe_problem_base.currentNonlinearSystem(); + nonlinear_sys.update(); + + // Get the penetration locator from the displaced mesh if it exist, otherwise get + // it from the undisplaced mesh. + const auto displaced_problem = fe_problem_base.getDisplacedProblem(); + const auto & penetration_locators = (displaced_problem ? displaced_problem->geomSearchData() + : fe_problem_base.geomSearchData()) + ._penetration_locators; + + // loop over contact pairs (penetration locators) + const ConstraintWarehouse & constraints = nonlinear_sys.getConstraintWarehouse(); + std::list> mccs; + for (const auto & pair : penetration_locators) + { + const auto & boundaries = pair.first; + + if (!constraints.hasActiveNodeFaceConstraints(boundaries.second, bool(displaced_problem))) + continue; + const auto & ncs = + constraints.getActiveNodeFaceConstraints(boundaries.second, bool(displaced_problem)); + + mccs.emplace_back(nullptr); + for (const auto & nc : ncs) + if (const auto mcc = std::dynamic_pointer_cast(nc); !mcc) + mooseError("AugmentedLagrangianContactProblem: dynamic cast of " + "MechanicalContactConstraint object failed."); + else + { + // Return if this constraint does not correspond to the primary-secondary pair + // prepared by the outer loops. + // This continue statement is required when, e.g. one secondary surface constrains + // more than one primary surface. + if (mcc->secondaryBoundary() != boundaries.second || + mcc->primaryBoundary() != boundaries.first) + continue; + + // save one constraint pointer for each contact pair + if (!mccs.back()) + mccs.back() = mcc; + + // check if any of the constraints is not yet converged + if (repeat_augmented_lagrange_step || !mcc->AugmentedLagrangianContactConverged()) + { + repeat_augmented_lagrange_step = true; + break; + } + } + } + + // next loop over penalty mortar user objects + const auto & pmuos = this->_app.template getInterfaceObjects(); + for (auto * pmuo : pmuos) + { + // check if any of the constraints is not yet converged + if (!repeat_augmented_lagrange_step && !pmuo->isAugmentedLagrangianConverged()) + repeat_augmented_lagrange_step = true; + } + + // Communicate the repeat_augmented_lagrange_step in parallel. + // If one proc needs to do another loop, all do. + this->_communicator.max(repeat_augmented_lagrange_step); + + // repeat update step if necessary + if (repeat_augmented_lagrange_step) + { + _lagrangian_iteration_number++; + Moose::out << "Augmented Lagrangian contact repeat " << _lagrangian_iteration_number + << '\n'; + + // Each contact pair will have constraints for all displacements, but those share the + // Lagrange multipliers, which are stored on the penetration locator. We call update + // only for the first constraint for each contact pair. + for (const auto & mcc : mccs) + mcc->updateAugmentedLagrangianMultiplier(/* beginning_of_step = */ false); + + // Update all penalty mortar user objects + for (const auto & pmuo : pmuos) + pmuo->updateAugmentedLagrangianMultipliers(); + + // call AM setup again (e.g. to update active sets) + for (const auto & pmuo : pmuos) + pmuo->augmentedLagrangianSetup(); + + // force it to keep iterating + reason = Convergence::MooseConvergenceStatus::ITERATING; + Moose::out << "Augmented Lagrangian Multiplier needs updating."; + } + else + Moose::out << "Augmented Lagrangian contact constraint enforcement is satisfied."; + } + else + { + // maxed out + Moose::out << "Maximum Augmented Lagrangian contact iterations have been reached."; + reason = Convergence::MooseConvergenceStatus::DIVERGED; + } + } + + return reason; +} + +template class AugmentedLagrangianContactConvergence; +template class AugmentedLagrangianContactConvergence; diff --git a/modules/contact/src/problems/AugmentedLagrangianContactProblem.C b/modules/contact/src/problems/AugmentedLagrangianContactProblem.C index d6ddcb3ec256..4526d6ddd3bb 100644 --- a/modules/contact/src/problems/AugmentedLagrangianContactProblem.C +++ b/modules/contact/src/problems/AugmentedLagrangianContactProblem.C @@ -26,26 +26,12 @@ #include "ConstraintWarehouse.h" #include "MortarUserObject.h" #include "AugmentedLagrangeInterface.h" +#include "AugmentedLagrangianContactConvergence.h" +#include "Convergence.h" registerMooseObject("ContactApp", AugmentedLagrangianContactProblem); registerMooseObject("ContactApp", AugmentedLagrangianContactFEProblem); -InputParameters -AugmentedLagrangianContactProblemInterface::validParams() -{ - auto params = emptyInputParameters(); - params.addParam("maximum_lagrangian_update_iterations", - 100, - "Maximum number of update Lagrangian Multiplier iterations per step"); - return params; -} - -AugmentedLagrangianContactProblemInterface::AugmentedLagrangianContactProblemInterface( - const InputParameters & params) - : _maximum_number_lagrangian_iterations(params.get("maximum_lagrangian_update_iterations")) -{ -} - template InputParameters AugmentedLagrangianContactProblemTempl::validParams() @@ -71,147 +57,30 @@ AugmentedLagrangianContactProblemTempl::timestepSetup() T::timestepSetup(); } -template -MooseNonlinearConvergenceReason -AugmentedLagrangianContactProblemTempl::checkNonlinearConvergence(std::string & msg, - const PetscInt it, - const Real xnorm, - const Real snorm, - const Real fnorm, - const Real rtol, - const Real divtol, - const Real stol, - const Real abstol, - const PetscInt nfuncs, - const PetscInt /*max_funcs*/, - const Real /*div_threshold*/) +template <> +void +AugmentedLagrangianContactProblemTempl::addDefaultNonlinearConvergence( + const InputParameters & params_to_apply) { - Real my_max_funcs = std::numeric_limits::max(); - Real my_div_threshold = std::numeric_limits::max(); - - MooseNonlinearConvergenceReason reason = T::checkNonlinearConvergence(msg, - it, - xnorm, - snorm, - fnorm, - rtol, - divtol, - stol, - abstol, - nfuncs, - my_max_funcs, - my_div_threshold); - - _console << "Augmented Lagrangian contact iteration " << _lagrangian_iteration_number - << std::endl; - - bool repeat_augmented_lagrange_step = false; - - if (reason == MooseNonlinearConvergenceReason::CONVERGED_FNORM_ABS || - reason == MooseNonlinearConvergenceReason::CONVERGED_FNORM_RELATIVE || - reason == MooseNonlinearConvergenceReason::CONVERGED_SNORM_RELATIVE) - { - if (_lagrangian_iteration_number < _maximum_number_lagrangian_iterations) - { - auto & nonlinear_sys = currentNonlinearSystem(); - nonlinear_sys.update(); - - // Get the penetration locator from the displaced mesh if it exist, otherwise get - // it from the undisplaced mesh. - const auto displaced_problem = getDisplacedProblem(); - const auto & penetration_locators = - (displaced_problem ? displaced_problem->geomSearchData() : geomSearchData()) - ._penetration_locators; - - // loop over contact pairs (penetration locators) - const ConstraintWarehouse & constraints = nonlinear_sys.getConstraintWarehouse(); - std::list> mccs; - for (const auto & pair : penetration_locators) - { - const auto & boundaries = pair.first; - - if (!constraints.hasActiveNodeFaceConstraints(boundaries.second, bool(displaced_problem))) - continue; - const auto & ncs = - constraints.getActiveNodeFaceConstraints(boundaries.second, bool(displaced_problem)); - - mccs.emplace_back(nullptr); - for (const auto & nc : ncs) - if (const auto mcc = std::dynamic_pointer_cast(nc); !mcc) - mooseError("AugmentedLagrangianContactProblem: dynamic cast of " - "MechanicalContactConstraint object failed."); - else - { - // Return if this constraint does not correspond to the primary-secondary pair - // prepared by the outer loops. - // This continue statement is required when, e.g. one secondary surface constrains - // more than one primary surface. - if (mcc->secondaryBoundary() != boundaries.second || - mcc->primaryBoundary() != boundaries.first) - continue; - - // save one constraint pointer for each contact pair - if (!mccs.back()) - mccs.back() = mcc; - - // check if any of the constraints is not yet converged - if (repeat_augmented_lagrange_step || !mcc->AugmentedLagrangianContactConverged()) - { - repeat_augmented_lagrange_step = true; - break; - } - } - } - - // next loop over penalty mortar user objects - const auto & pmuos = this->_app.template getInterfaceObjects(); - for (auto * pmuo : pmuos) - { - // check if any of the constraints is not yet converged - if (!repeat_augmented_lagrange_step && !pmuo->isAugmentedLagrangianConverged()) - repeat_augmented_lagrange_step = true; - } - - // Communicate the repeat_augmented_lagrange_step in parallel. - // If one proc needs to do another loop, all do. - this->_communicator.max(repeat_augmented_lagrange_step); - - // repeat update step if necessary - if (repeat_augmented_lagrange_step) - { - _lagrangian_iteration_number++; - - // Each contact pair will have constraints for all displacements, but those share the - // Lagrange multipliers, which are stored on the penetration locator. We call update - // only for the first constraint for each contact pair. - for (const auto & mcc : mccs) - mcc->updateAugmentedLagrangianMultiplier(/* beginning_of_step = */ false); - - // Update all penalty mortar user objects - for (const auto & pmuo : pmuos) - pmuo->updateAugmentedLagrangianMultipliers(); - - // call AM setup again (e.g. to update active sets) - for (const auto & pmuo : pmuos) - pmuo->augmentedLagrangianSetup(); - - // force it to keep iterating - reason = MooseNonlinearConvergenceReason::ITERATING; - _console << "Augmented Lagrangian Multiplier needs updating." << std::endl; - } - else - _console << "Augmented Lagrangian contact constraint enforcement is satisfied." - << std::endl; - } - else - { - // maxed out - _console << "Maximum Augmented Lagrangian contact iterations have been reached." << std::endl; - reason = MooseNonlinearConvergenceReason::DIVERGED_FUNCTION_COUNT; - } - } + std::string class_name = "AugmentedLagrangianContactReferenceConvergence"; + InputParameters params = this->_factory.getValidParams(class_name); + params.applyParameters(params_to_apply); + params.applyParameters(parameters()); + params.set("added_as_default") = true; + this->addConvergence(class_name, this->getNonlinearConvergenceName(), params); +} - return reason; +template <> +void +AugmentedLagrangianContactProblemTempl::addDefaultNonlinearConvergence( + const InputParameters & params_to_apply) +{ + std::string class_name = "AugmentedLagrangianContactFEProblemConvergence"; + InputParameters params = _factory.getValidParams(class_name); + params.applyParameters(params_to_apply); + params.applyParameters(parameters()); + params.set("added_as_default") = true; + this->addConvergence(class_name, this->getNonlinearConvergenceName(), params); } template class AugmentedLagrangianContactProblemTempl; diff --git a/modules/contact/src/problems/AugmentedLagrangianContactProblemInterface.C b/modules/contact/src/problems/AugmentedLagrangianContactProblemInterface.C new file mode 100644 index 000000000000..11d96e7c16bc --- /dev/null +++ b/modules/contact/src/problems/AugmentedLagrangianContactProblemInterface.C @@ -0,0 +1,26 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#include "AugmentedLagrangianContactProblemInterface.h" + +InputParameters +AugmentedLagrangianContactProblemInterface::validParams() +{ + auto params = emptyInputParameters(); + params.addParam("maximum_lagrangian_update_iterations", + 100, + "Maximum number of update Lagrangian Multiplier iterations per step"); + return params; +} + +AugmentedLagrangianContactProblemInterface::AugmentedLagrangianContactProblemInterface( + const InputParameters & params) + : _maximum_number_lagrangian_iterations(params.get("maximum_lagrangian_update_iterations")) +{ +} diff --git a/modules/doc/content/newsletter/2024/2024_10.md b/modules/doc/content/newsletter/2024/2024_10.md index 17eaff2094d7..089cc339e1c7 100644 --- a/modules/doc/content/newsletter/2024/2024_10.md +++ b/modules/doc/content/newsletter/2024/2024_10.md @@ -7,6 +7,16 @@ for a complete description of all MOOSE changes. ## MOOSE Improvements +### New System: Convergence + +The [Convergence](syntax/Convergence/index.md) system was added, which allows +the user to specify customized stopping criteria for their solves/iteration. +Currently, only the nonlinear solve is supported, but this is planned for +extension to fixed point iteration and steady-state detection. The main class +to add new capability is [PostprocessorConvergence.md], which allows a post-processor +to be compared to a tolerance to determine convergence. This post-processor +should be executed on the new `execute_on` option, `NONLINEAR_CONVERGENCE`. + ## libMesh-level Changes ### `2024.10.17` Update @@ -52,5 +62,5 @@ for a complete description of all MOOSE changes. additional Conda packages. - HDF5 has been downgraded to solve issue: [Floating Point Exception, #28350](https://github.com/idaholab/moose/issues/28350) - The minimum MacOS build SDK has been bumped to MacOS Big Sur (11.3). Users of - MacOS versions older than this may run into issues building and running MOOSE, + MacOS versions older than this may run into issues building and running MOOSE, MOOSE dependencies, or other MOOSE-based applications. diff --git a/test/include/convergence/SuppliedStatusConvergence.h b/test/include/convergence/SuppliedStatusConvergence.h new file mode 100644 index 000000000000..3b9e6adf8989 --- /dev/null +++ b/test/include/convergence/SuppliedStatusConvergence.h @@ -0,0 +1,29 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#pragma once + +#include "IterationCountConvergence.h" + +/** + * Takes a user-supplied vector of convergence statuses for each iteration. + */ +class SuppliedStatusConvergence : public IterationCountConvergence +{ +public: + static InputParameters validParams(); + + SuppliedStatusConvergence(const InputParameters & parameters); + +protected: + virtual MooseConvergenceStatus checkConvergenceInner(unsigned int iter) override; + + /// Convergence status for each iteration + const std::vector & _convergence_statuses; +}; diff --git a/test/src/convergence/SuppliedStatusConvergence.C b/test/src/convergence/SuppliedStatusConvergence.C new file mode 100644 index 000000000000..7e69fcc47732 --- /dev/null +++ b/test/src/convergence/SuppliedStatusConvergence.C @@ -0,0 +1,57 @@ +//* This file is part of the MOOSE framework +//* https://www.mooseframework.org +//* +//* All rights reserved, see COPYRIGHT for full restrictions +//* https://github.com/idaholab/moose/blob/master/COPYRIGHT +//* +//* Licensed under LGPL 2.1, please see LICENSE for details +//* https://www.gnu.org/licenses/lgpl-2.1.html + +#include "SuppliedStatusConvergence.h" + +registerMooseObject("MooseTestApp", SuppliedStatusConvergence); + +InputParameters +SuppliedStatusConvergence::validParams() +{ + InputParameters params = IterationCountConvergence::validParams(); + + params.addRequiredParam>( + "convergence_statuses", + "List of convergence status integers for each iteration. Valid values are: -1 for diverged, " + "0 for iterating, and 1 for converged."); + + params.addClassDescription( + "Takes a user-supplied vector of convergence statuses for each iteration."); + + return params; +} + +SuppliedStatusConvergence::SuppliedStatusConvergence(const InputParameters & parameters) + : IterationCountConvergence(parameters), + _convergence_statuses(getParam>("convergence_statuses")) +{ +} + +Convergence::MooseConvergenceStatus +SuppliedStatusConvergence::checkConvergenceInner(unsigned int iter) +{ + if (iter > _convergence_statuses.size() - 1) + mooseError("Iteration index greater than last index of 'convergence_statuses'."); + + switch (_convergence_statuses[iter]) + { + case -1: + return MooseConvergenceStatus::DIVERGED; + break; + case 0: + return MooseConvergenceStatus::ITERATING; + break; + case 1: + return MooseConvergenceStatus::CONVERGED; + break; + default: + mooseError("Invalid status integer in 'convergence_statuses'. Valid values are: -1 for " + "diverged, 0 for iterating, and 1 for converged."); + } +} diff --git a/test/tests/convergence/default_nonlinear_convergence/default_nonlinear_convergence.i b/test/tests/convergence/default_nonlinear_convergence/default_nonlinear_convergence.i new file mode 100644 index 000000000000..536010574324 --- /dev/null +++ b/test/tests/convergence/default_nonlinear_convergence/default_nonlinear_convergence.i @@ -0,0 +1,62 @@ +[Mesh] + type = GeneratedMesh + dim = 2 + nx = 10 + ny = 10 +[] + +[Variables] + [u] + [] +[] + +[Kernels] + [diff] + type = Diffusion + variable = u + [] +[] + +[BCs] + [left] + type = DirichletBC + variable = u + boundary = left + value = 0 + [] + [right] + type = DirichletBC + variable = u + boundary = right + value = 1 + [] +[] + +[Convergence] + [res_conv] + type = DefaultNonlinearConvergence + # With default tolerances, the solve takes 2 iterations, but with this + # value, the solve takes only 1: + nl_abs_tol = 1e-5 + [] +[] + +[Postprocessors] + [num_nl_its] + type = NumNonlinearIterations + execute_on = 'TIMESTEP_END' + [] +[] + +[Executioner] + type = Steady + solve_type = 'PJFNK' + petsc_options_iname = '-pc_type' + petsc_options_value = 'hypre' + nonlinear_convergence = res_conv +[] + +[Outputs] + csv = true + execute_on = 'FINAL' +[] diff --git a/test/tests/convergence/default_nonlinear_convergence/gold/default_nonlinear_convergence_out.csv b/test/tests/convergence/default_nonlinear_convergence/gold/default_nonlinear_convergence_out.csv new file mode 100644 index 000000000000..5f09f2d350a7 --- /dev/null +++ b/test/tests/convergence/default_nonlinear_convergence/gold/default_nonlinear_convergence_out.csv @@ -0,0 +1,2 @@ +time,num_nl_its +2,1 diff --git a/test/tests/convergence/default_nonlinear_convergence/tests b/test/tests/convergence/default_nonlinear_convergence/tests new file mode 100644 index 000000000000..9b3050860639 --- /dev/null +++ b/test/tests/convergence/default_nonlinear_convergence/tests @@ -0,0 +1,29 @@ +[Tests] + design = 'DefaultNonlinearConvergence.md' + issues = '#25931' + + [test] + type = CSVDiff + input = 'default_nonlinear_convergence.i' + csvdiff = 'default_nonlinear_convergence_out.csv' + requirement = 'The system shall be able to determine convergence of the nonlinear solve using default criteria through an object.' + [] + [error_reporting] + requirement = 'The system shall report an error for the default convergence class' + [multiple_objects] + type = RunException + input = 'default_nonlinear_convergence.i' + cli_args = "Convergence/conv2/type=DefaultNonlinearConvergence" + expect_err = "Only one DefaultNonlinearConvergence may be constructed" + detail = 'if multiple instances are created.' + skip = '#28936' + [] + [conflicting_criteria] + type = RunException + input = 'default_nonlinear_convergence.i' + cli_args = "Executioner/nl_abs_tol=1e-20" + expect_err = "The following parameters were set in both this Convergence object and the executioner:\s+nl_abs_tol" + detail = 'if convergence criteria are additionally specified in the executioner.' + [] + [] +[] diff --git a/test/tests/convergence/iteration_count_convergence/base.i b/test/tests/convergence/iteration_count_convergence/base.i new file mode 100644 index 000000000000..87e79ec61864 --- /dev/null +++ b/test/tests/convergence/iteration_count_convergence/base.i @@ -0,0 +1,62 @@ +[Mesh] + type = GeneratedMesh + dim = 2 + nx = 10 + ny = 10 +[] + +[Variables] + [u] + [] +[] + +[Kernels] + [diff] + type = Diffusion + variable = u + [] +[] + +[BCs] + [left] + type = DirichletBC + variable = u + boundary = left + value = 0 + [] + [right] + type = DirichletBC + variable = u + boundary = right + value = 1 + [] +[] + +[Postprocessors] + [nonlin_its] + type = NumNonlinearIterations + execute_on = 'TIMESTEP_END' + [] +[] + +[Convergence] + [test_conv] + type = SuppliedStatusConvergence + min_iterations = 2 + max_iterations = 4 + [] +[] + +[Executioner] + type = Steady + solve_type = PJFNK + line_search = none + petsc_options_iname = '-pc_type' + petsc_options_value = 'hypre' + nonlinear_convergence = test_conv +[] + +[Outputs] + csv = true + execute_on = 'FINAL' +[] diff --git a/test/tests/convergence/iteration_count_convergence/gold/max_iterations_converge_out.csv b/test/tests/convergence/iteration_count_convergence/gold/max_iterations_converge_out.csv new file mode 100644 index 000000000000..527316132644 --- /dev/null +++ b/test/tests/convergence/iteration_count_convergence/gold/max_iterations_converge_out.csv @@ -0,0 +1,2 @@ +time,nonlin_its +2,4 diff --git a/test/tests/convergence/iteration_count_convergence/gold/min_iterations_out.csv b/test/tests/convergence/iteration_count_convergence/gold/min_iterations_out.csv new file mode 100644 index 000000000000..3c0df71e1d7d --- /dev/null +++ b/test/tests/convergence/iteration_count_convergence/gold/min_iterations_out.csv @@ -0,0 +1,2 @@ +time,nonlin_its +2,2 diff --git a/test/tests/convergence/iteration_count_convergence/max_iterations_converge.i b/test/tests/convergence/iteration_count_convergence/max_iterations_converge.i new file mode 100644 index 000000000000..d4b4f5c627cf --- /dev/null +++ b/test/tests/convergence/iteration_count_convergence/max_iterations_converge.i @@ -0,0 +1,8 @@ +!include base.i + +[Convergence] + [test_conv] + convergence_statuses = '0 0 0 0 0 0 0' + converge_at_max_iterations = true + [] +[] diff --git a/test/tests/convergence/iteration_count_convergence/max_iterations_diverge.i b/test/tests/convergence/iteration_count_convergence/max_iterations_diverge.i new file mode 100644 index 000000000000..0d09f25bd69b --- /dev/null +++ b/test/tests/convergence/iteration_count_convergence/max_iterations_diverge.i @@ -0,0 +1,7 @@ +!include base.i + +[Convergence] + [test_conv] + convergence_statuses = '0 0 0 0 0 0 0' + [] +[] diff --git a/test/tests/convergence/iteration_count_convergence/min_iterations.i b/test/tests/convergence/iteration_count_convergence/min_iterations.i new file mode 100644 index 000000000000..c2a872c9f2b3 --- /dev/null +++ b/test/tests/convergence/iteration_count_convergence/min_iterations.i @@ -0,0 +1,7 @@ +!include base.i + +[Convergence] + [test_conv] + convergence_statuses = '1 1 1 1 1 1 1' + [] +[] diff --git a/test/tests/convergence/iteration_count_convergence/tests b/test/tests/convergence/iteration_count_convergence/tests new file mode 100644 index 000000000000..0d942fd8bdfa --- /dev/null +++ b/test/tests/convergence/iteration_count_convergence/tests @@ -0,0 +1,30 @@ +[Tests] + design = 'IterationCountConvergence.md' + issues = '#24128' + + [min_iterations] + type = CSVDiff + input = 'min_iterations.i' + csvdiff = 'min_iterations_out.csv' + requirement = 'The system shall be able to specify a minimum number of iterations.' + [] + [max_iterations_converge] + type = CSVDiff + input = 'max_iterations_converge.i' + csvdiff = 'max_iterations_converge_out.csv' + requirement = 'The system shall be able to specify to converge after a certain number of iterations.' + [] + [max_iterations_diverge] + type = RunApp + input = 'max_iterations_diverge.i' + expect_out = "Solve Did NOT Converge" + requirement = 'The system shall be able to specify to diverge if not converged after a certain number of iterations.' + [] + [error_max_greater_than_min] + type = RunException + input = 'min_iterations.i' + cli_args = "Convergence/test_conv/max_iterations=1" + expect_err = "'max_iterations' must be >= 'min_iterations'" + requirement = 'The system shall report an error when the maximum number of iterations is less than the minimum number of iterations.' + [] +[] diff --git a/test/tests/convergence/postprocessor_convergence/gold/postprocessor_convergence_out.csv b/test/tests/convergence/postprocessor_convergence/gold/postprocessor_convergence_out.csv new file mode 100644 index 000000000000..a34d05bfe7af --- /dev/null +++ b/test/tests/convergence/postprocessor_convergence/gold/postprocessor_convergence_out.csv @@ -0,0 +1,2 @@ +time,num_nl_iterations +2,3 diff --git a/test/tests/convergence/postprocessor_convergence/postprocessor_convergence.i b/test/tests/convergence/postprocessor_convergence/postprocessor_convergence.i new file mode 100644 index 000000000000..1069f5f24915 --- /dev/null +++ b/test/tests/convergence/postprocessor_convergence/postprocessor_convergence.i @@ -0,0 +1,89 @@ +[Mesh] + type = GeneratedMesh + dim = 2 + nx = 10 + ny = 10 +[] + +[Variables] + [u] + [] +[] + +[AuxVariables] + [w] + [] +[] + +[AuxKernels] + [w_kernel] + type = ParsedAux + variable = w + expression = 'sol' + functor_names = 'u' + functor_symbols = 'sol' + execute_on = 'NONLINEAR_CONVERGENCE' + [] +[] + +[Kernels] + [diff] + type = Diffusion + variable = u + [] +[] + +[BCs] + [left] + type = DirichletBC + variable = u + boundary = left + value = 0 + [] + [right] + type = DirichletBC + variable = u + boundary = right + value = 1 + [] +[] + +[Postprocessors] + [w_change] + type = AverageVariableChange + variable = w + change_over = nonlinear_iteration + norm = L1 + execute_on = 'NONLINEAR_CONVERGENCE' + [] + [num_nl_iterations] + type = NumNonlinearIterations + execute_on = 'TIMESTEP_END' + [] +[] + +[Convergence] + [test_conv] + type = PostprocessorConvergence + postprocessor = w_change + tolerance = 1e-7 + [] +[] + +[Problem] + previous_nl_solution_required = true +[] + +[Executioner] + type = Steady + solve_type = 'PJFNK' + petsc_options_iname = '-pc_type' + petsc_options_value = 'hypre' + nonlinear_convergence = test_conv +[] + +[Outputs] + csv = true + show = 'num_nl_iterations' + execute_on = 'FINAL' +[] diff --git a/test/tests/convergence/postprocessor_convergence/tests b/test/tests/convergence/postprocessor_convergence/tests new file mode 100644 index 000000000000..fff248930366 --- /dev/null +++ b/test/tests/convergence/postprocessor_convergence/tests @@ -0,0 +1,11 @@ +[Tests] + design = 'PostprocessorConvergence.md' + issues = '#28765' + + [test] + type = CSVDiff + input = 'postprocessor_convergence.i' + csvdiff = 'postprocessor_convergence_out.csv' + requirement = 'The system shall be able to terminate iteration according to a post-processor value.' + [] +[] diff --git a/test/tests/convergence/reference_residual_convergence/abs_ref.i b/test/tests/convergence/reference_residual_convergence/abs_ref.i new file mode 100644 index 000000000000..cca8d50c9bf0 --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/abs_ref.i @@ -0,0 +1,119 @@ +[Mesh] + type = GeneratedMesh + dim = 1 + nx = 10 +[] + +[GlobalParams] + absolute_value_vector_tags = 'absref' +[] + +[Problem] + extra_tag_vectors = 'absref' +[] + +[Variables] + [u][] + [v] + scaling = 1e-6 + [] +[] + +[Functions] + [ramp] + type = ParsedFunction + expression = 'if(t < 5, t - 5, 0) * x' + [] +[] + +[Kernels] + [u_dt] + type = TimeDerivative + variable = u + [] + [u_coupled_rx] + type = CoupledForce + variable = u + v = v + coef = 1 + [] + + [v_dt] + type = TimeDerivative + variable = v + [] + [v_neg_force] + type = BodyForce + variable = v + value = ${fparse -1 / 2} + function = ramp + [] + [v_force] + type = BodyForce + variable = v + value = 1 + function = ramp + [] +[] + +[Postprocessors] + [u_avg] + type = ElementAverageValue + variable = u + execute_on = 'TIMESTEP_END INITIAL' + [] + [v_avg] + type = ElementAverageValue + variable = v + execute_on = 'TIMESTEP_END INITIAL' + [] + [timestep] + type = TimePostprocessor + outputs = 'none' + [] + [v_old] + type = ElementAverageValue + variable = v + execute_on = TIMESTEP_BEGIN + outputs = none + [] + [u_old] + type = ElementAverageValue + variable = u + execute_on = TIMESTEP_BEGIN + outputs = none + [] + [v_exact] + type = ParsedPostprocessor + pp_names = 'timestep v_old' + expression = 't := if(timestep > 5, 5, timestep); (t^2 - 9 * t) / 8' + [] + [u_exact] + type = ParsedPostprocessor + pp_names = 'u_old v_exact' + expression = 'u_old + v_exact' + [] +[] + +[Convergence] + [conv] + type = ReferenceResidualConvergence + reference_vector = 'absref' + [] +[] + +[Executioner] + type = Transient + petsc_options = '-snes_converged_reason' + petsc_options_iname = '-pc_type' + petsc_options_value = 'lu' + line_search = none + num_steps = 10 + nl_rel_tol = 1e-06 + nonlinear_convergence = conv + verbose = true +[] + +[Outputs] + csv = true +[] diff --git a/test/tests/convergence/reference_residual_convergence/abs_ref_acceptable.i b/test/tests/convergence/reference_residual_convergence/abs_ref_acceptable.i new file mode 100644 index 000000000000..41a477e2d454 --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/abs_ref_acceptable.i @@ -0,0 +1,122 @@ +[Mesh] + type = GeneratedMesh + dim = 1 + nx = 10 +[] + +[GlobalParams] + absolute_value_vector_tags = 'absref' +[] + +[Problem] + extra_tag_vectors = 'absref' +[] + +[Variables] + [u][] + [v] + scaling = 1e-6 + [] +[] + +[Functions] + [ramp] + type = ParsedFunction + expression = 'if(t < 5, t - 5, 0) * x' + [] +[] + +[Kernels] + [u_dt] + type = TimeDerivative + variable = u + [] + [u_coupled_rx] + type = CoupledForce + variable = u + v = v + coef = 1 + [] + + [v_dt] + type = TimeDerivative + variable = v + [] + [v_neg_force] + type = BodyForce + variable = v + value = ${fparse -1 / 2} + function = ramp + [] + [v_force] + type = BodyForce + variable = v + value = 1 + function = ramp + [] +[] + +[Postprocessors] + [u_avg] + type = ElementAverageValue + variable = u + execute_on = 'TIMESTEP_END INITIAL' + [] + [v_avg] + type = ElementAverageValue + variable = v + execute_on = 'TIMESTEP_END INITIAL' + [] + [timestep] + type = TimePostprocessor + outputs = 'none' + [] + [v_old] + type = ElementAverageValue + variable = v + execute_on = TIMESTEP_BEGIN + outputs = none + [] + [u_old] + type = ElementAverageValue + variable = u + execute_on = TIMESTEP_BEGIN + outputs = none + [] + [v_exact] + type = ParsedPostprocessor + pp_names = 'timestep v_old' + expression = 't := if(timestep > 5, 5, timestep); (t^2 - 9 * t) / 8' + [] + [u_exact] + type = ParsedPostprocessor + pp_names = 'u_old v_exact' + expression = 'u_old + v_exact' + [] +[] + +[Convergence] + [conv] + type = ReferenceResidualConvergence + reference_vector = 'absref' + acceptable_iterations = 1 + acceptable_multiplier = 1e6 + [] +[] + +[Executioner] + type = Transient + petsc_options = '-snes_converged_reason' + petsc_options_iname = '-pc_type' + petsc_options_value = 'lu' + line_search = none + num_steps = 3 + nl_rel_tol = 1e-06 + nonlinear_convergence = conv + verbose = true +[] + +[Outputs] + csv = true + perf_graph = true +[] diff --git a/test/tests/convergence/reference_residual_convergence/ad_abs_ref.i b/test/tests/convergence/reference_residual_convergence/ad_abs_ref.i new file mode 100644 index 000000000000..c686c27a23ab --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/ad_abs_ref.i @@ -0,0 +1,119 @@ +[Mesh] + type = GeneratedMesh + dim = 1 + nx = 10 +[] + +[GlobalParams] + absolute_value_vector_tags = 'absref' +[] + +[Problem] + extra_tag_vectors = 'absref' +[] + +[Variables] + [u][] + [v] + scaling = 1e-6 + [] +[] + +[Functions] + [ramp] + type = ParsedFunction + expression = 'if(t < 5, t - 5, 0) * x' + [] +[] + +[Kernels] + [u_dt] + type = ADTimeDerivative + variable = u + [] + [u_coupled_rx] + type = ADCoupledForce + variable = u + v = v + coef = 1 + [] + + [v_dt] + type = ADTimeDerivative + variable = v + [] + [v_neg_force] + type = ADBodyForce + variable = v + value = ${fparse -1 / 2} + function = ramp + [] + [v_force] + type = ADBodyForce + variable = v + value = 1 + function = ramp + [] +[] + +[Postprocessors] + [u_avg] + type = ElementAverageValue + variable = u + execute_on = 'TIMESTEP_END INITIAL' + [] + [v_avg] + type = ElementAverageValue + variable = v + execute_on = 'TIMESTEP_END INITIAL' + [] + [timestep] + type = TimePostprocessor + outputs = 'none' + [] + [v_old] + type = ElementAverageValue + variable = v + execute_on = TIMESTEP_BEGIN + outputs = none + [] + [u_old] + type = ElementAverageValue + variable = u + execute_on = TIMESTEP_BEGIN + outputs = none + [] + [v_exact] + type = ParsedPostprocessor + pp_names = 'timestep v_old' + expression = 't := if(timestep > 5, 5, timestep); (t^2 - 9 * t) / 8' + [] + [u_exact] + type = ParsedPostprocessor + pp_names = 'u_old v_exact' + expression = 'u_old + v_exact' + [] +[] + +[Convergence] + [conv] + type = ReferenceResidualConvergence + reference_vector = 'absref' + [] +[] + +[Executioner] + type = Transient + petsc_options = '-snes_converged_reason' + petsc_options_iname = '-pc_type' + petsc_options_value = 'lu' + line_search = none + num_steps = 10 + nl_rel_tol = 1e-06 + nonlinear_convergence = conv + verbose = true +[] + +[Outputs] + csv = true +[] diff --git a/test/tests/convergence/reference_residual_convergence/gold/abs_ref_Linf_out.csv b/test/tests/convergence/reference_residual_convergence/gold/abs_ref_Linf_out.csv new file mode 100644 index 000000000000..216cbf0ff827 --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/gold/abs_ref_Linf_out.csv @@ -0,0 +1,12 @@ +time,u_avg,u_exact,v_avg,v_exact +0,0,0,0,0 +1,-1.0000000009967,-1,-1.0000000009967,-1 +2,-2.7500000012673,-2.7500000009967,-1.7500000003897,-1.75 +3,-5.0000000019665,-5.0000000012673,-2.2500000006175,-2.25 +4,-7.5000000025815,-7.5000000019665,-2.5000000006169,-2.5 +5,-10.000000039118,-10.000000002581,-2.5000000006169,-2.5 +6,-12.500000024675,-12.500000039118,-2.5000000006169,-2.5 +7,-15.000000021527,-15.000000024675,-2.5000000006169,-2.5 +8,-17.500000026255,-17.500000021527,-2.5000000006169,-2.5 +9,-19.999999962567,-20.000000026255,-2.5000000006169,-2.5 +10,-22.49999993154,-22.499999962567,-2.5000000006169,-2.5 diff --git a/test/tests/convergence/reference_residual_convergence/gold/abs_ref_acceptable_out.csv b/test/tests/convergence/reference_residual_convergence/gold/abs_ref_acceptable_out.csv new file mode 100644 index 000000000000..6b7acf7edc1e --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/gold/abs_ref_acceptable_out.csv @@ -0,0 +1,12 @@ +time,u_avg,u_exact,v_avg,v_exact +0,0,0,0,0 +1,-1.0000000009967,-1,-1.0000000009967,-1 +2,-2.7500000012673,-2.7500000009967,-1.7500000003897,-1.75 +3,-5.0012099753725,-5.0000000012673,-2.251106763104,-2.25 +4,-7.5020377332576,-7.5012099753725,-2.5011402264065,-2.5 +5,-10.003177964795,-10.002037733258,-2.5011402264065,-2.5 +6,-12.504318193886,-12.503177964795,-2.5011402264065,-2.5 +7,-15.005458396275,-15.004318193886,-2.5011402264065,-2.5 +8,-17.506598652312,-17.505458396275,-2.5011402264065,-2.5 +9,-20.007738863101,-20.006598652312,-2.5011402264065,-2.5 +10,-22.50887907897,-22.507738863101,-2.5011402264065,-2.5 diff --git a/test/tests/convergence/reference_residual_convergence/gold/abs_ref_local_Linf_out.csv b/test/tests/convergence/reference_residual_convergence/gold/abs_ref_local_Linf_out.csv new file mode 100644 index 000000000000..216cbf0ff827 --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/gold/abs_ref_local_Linf_out.csv @@ -0,0 +1,12 @@ +time,u_avg,u_exact,v_avg,v_exact +0,0,0,0,0 +1,-1.0000000009967,-1,-1.0000000009967,-1 +2,-2.7500000012673,-2.7500000009967,-1.7500000003897,-1.75 +3,-5.0000000019665,-5.0000000012673,-2.2500000006175,-2.25 +4,-7.5000000025815,-7.5000000019665,-2.5000000006169,-2.5 +5,-10.000000039118,-10.000000002581,-2.5000000006169,-2.5 +6,-12.500000024675,-12.500000039118,-2.5000000006169,-2.5 +7,-15.000000021527,-15.000000024675,-2.5000000006169,-2.5 +8,-17.500000026255,-17.500000021527,-2.5000000006169,-2.5 +9,-19.999999962567,-20.000000026255,-2.5000000006169,-2.5 +10,-22.49999993154,-22.499999962567,-2.5000000006169,-2.5 diff --git a/test/tests/convergence/reference_residual_convergence/gold/abs_ref_local_out.csv b/test/tests/convergence/reference_residual_convergence/gold/abs_ref_local_out.csv new file mode 100644 index 000000000000..216cbf0ff827 --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/gold/abs_ref_local_out.csv @@ -0,0 +1,12 @@ +time,u_avg,u_exact,v_avg,v_exact +0,0,0,0,0 +1,-1.0000000009967,-1,-1.0000000009967,-1 +2,-2.7500000012673,-2.7500000009967,-1.7500000003897,-1.75 +3,-5.0000000019665,-5.0000000012673,-2.2500000006175,-2.25 +4,-7.5000000025815,-7.5000000019665,-2.5000000006169,-2.5 +5,-10.000000039118,-10.000000002581,-2.5000000006169,-2.5 +6,-12.500000024675,-12.500000039118,-2.5000000006169,-2.5 +7,-15.000000021527,-15.000000024675,-2.5000000006169,-2.5 +8,-17.500000026255,-17.500000021527,-2.5000000006169,-2.5 +9,-19.999999962567,-20.000000026255,-2.5000000006169,-2.5 +10,-22.49999993154,-22.499999962567,-2.5000000006169,-2.5 diff --git a/test/tests/convergence/reference_residual_convergence/gold/abs_ref_out.csv b/test/tests/convergence/reference_residual_convergence/gold/abs_ref_out.csv new file mode 100644 index 000000000000..216cbf0ff827 --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/gold/abs_ref_out.csv @@ -0,0 +1,12 @@ +time,u_avg,u_exact,v_avg,v_exact +0,0,0,0,0 +1,-1.0000000009967,-1,-1.0000000009967,-1 +2,-2.7500000012673,-2.7500000009967,-1.7500000003897,-1.75 +3,-5.0000000019665,-5.0000000012673,-2.2500000006175,-2.25 +4,-7.5000000025815,-7.5000000019665,-2.5000000006169,-2.5 +5,-10.000000039118,-10.000000002581,-2.5000000006169,-2.5 +6,-12.500000024675,-12.500000039118,-2.5000000006169,-2.5 +7,-15.000000021527,-15.000000024675,-2.5000000006169,-2.5 +8,-17.500000026255,-17.500000021527,-2.5000000006169,-2.5 +9,-19.999999962567,-20.000000026255,-2.5000000006169,-2.5 +10,-22.49999993154,-22.499999962567,-2.5000000006169,-2.5 diff --git a/test/tests/convergence/reference_residual_convergence/gold/ad_abs_ref_out.csv b/test/tests/convergence/reference_residual_convergence/gold/ad_abs_ref_out.csv new file mode 100644 index 000000000000..216cbf0ff827 --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/gold/ad_abs_ref_out.csv @@ -0,0 +1,12 @@ +time,u_avg,u_exact,v_avg,v_exact +0,0,0,0,0 +1,-1.0000000009967,-1,-1.0000000009967,-1 +2,-2.7500000012673,-2.7500000009967,-1.7500000003897,-1.75 +3,-5.0000000019665,-5.0000000012673,-2.2500000006175,-2.25 +4,-7.5000000025815,-7.5000000019665,-2.5000000006169,-2.5 +5,-10.000000039118,-10.000000002581,-2.5000000006169,-2.5 +6,-12.500000024675,-12.500000039118,-2.5000000006169,-2.5 +7,-15.000000021527,-15.000000024675,-2.5000000006169,-2.5 +8,-17.500000026255,-17.500000021527,-2.5000000006169,-2.5 +9,-19.999999962567,-20.000000026255,-2.5000000006169,-2.5 +10,-22.49999993154,-22.499999962567,-2.5000000006169,-2.5 diff --git a/test/tests/convergence/reference_residual_convergence/gold/no_ref_out.csv b/test/tests/convergence/reference_residual_convergence/gold/no_ref_out.csv new file mode 100644 index 000000000000..3e336f3be90c --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/gold/no_ref_out.csv @@ -0,0 +1,12 @@ +time,u_avg,u_exact,v_avg,v_exact +0,0,0,0,0 +1,-1.0000000009967,-1,-1.0000000009967,-1 +2,-2.7500000012673,-2.7500000009967,-1.7500000003897,-1.75 +3,-5.2000000173873,-5.0000000012673,-2.4500000048395,-2.25 +4,-7.9284090884818,-7.7000000173873,-2.7284090931768,-2.5 +5,-10.656818178762,-10.428409088482,-2.7284090931768,-2.5 +6,-13.385227268045,-13.156818178762,-2.7284090931768,-2.5 +7,-16.113636350814,-15.885227268045,-2.7284090931768,-2.5 +8,-18.842045455559,-18.613636350814,-2.7284090931768,-2.5 +9,-21.57045453968,-21.342045455559,-2.7284090931768,-2.5 +10,-24.298863665845,-24.07045453968,-2.7284090931768,-2.5 diff --git a/test/tests/convergence/reference_residual_convergence/gold/reference_residual_out.e b/test/tests/convergence/reference_residual_convergence/gold/reference_residual_out.e new file mode 100644 index 000000000000..2584f17bc689 Binary files /dev/null and b/test/tests/convergence/reference_residual_convergence/gold/reference_residual_out.e differ diff --git a/test/tests/convergence/reference_residual_convergence/gold/scaled.e b/test/tests/convergence/reference_residual_convergence/gold/scaled.e new file mode 100644 index 000000000000..2584f17bc689 Binary files /dev/null and b/test/tests/convergence/reference_residual_convergence/gold/scaled.e differ diff --git a/test/tests/convergence/reference_residual_convergence/gold/zero_rel_tolerance_ref_out.csv b/test/tests/convergence/reference_residual_convergence/gold/zero_rel_tolerance_ref_out.csv new file mode 100644 index 000000000000..216cbf0ff827 --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/gold/zero_rel_tolerance_ref_out.csv @@ -0,0 +1,12 @@ +time,u_avg,u_exact,v_avg,v_exact +0,0,0,0,0 +1,-1.0000000009967,-1,-1.0000000009967,-1 +2,-2.7500000012673,-2.7500000009967,-1.7500000003897,-1.75 +3,-5.0000000019665,-5.0000000012673,-2.2500000006175,-2.25 +4,-7.5000000025815,-7.5000000019665,-2.5000000006169,-2.5 +5,-10.000000039118,-10.000000002581,-2.5000000006169,-2.5 +6,-12.500000024675,-12.500000039118,-2.5000000006169,-2.5 +7,-15.000000021527,-15.000000024675,-2.5000000006169,-2.5 +8,-17.500000026255,-17.500000021527,-2.5000000006169,-2.5 +9,-19.999999962567,-20.000000026255,-2.5000000006169,-2.5 +10,-22.49999993154,-22.499999962567,-2.5000000006169,-2.5 diff --git a/test/tests/convergence/reference_residual_convergence/no_ref.i b/test/tests/convergence/reference_residual_convergence/no_ref.i new file mode 100644 index 000000000000..5100b1dc5e67 --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/no_ref.i @@ -0,0 +1,111 @@ +[Mesh] + type = GeneratedMesh + dim = 1 + nx = 10 +[] + +[Variables] + [u][] + [v] + scaling = 1e-6 + [] +[] + +[Functions] + [ramp] + type = ParsedFunction + expression = 'if(t < 5, t - 5, 0) * x' + [] +[] + +[Kernels] + [u_dt] + type = TimeDerivative + variable = u + [] + [u_coupled_rx] + type = CoupledForce + variable = u + v = v + coef = 1 + [] + + [v_dt] + type = TimeDerivative + variable = v + [] + [v_neg_force] + type = BodyForce + variable = v + value = ${fparse -1 / 2} + function = ramp + [] + [v_force] + type = BodyForce + variable = v + value = 1 + function = ramp + [] +[] + +[Postprocessors] + [u_avg] + type = ElementAverageValue + variable = u + execute_on = 'TIMESTEP_END INITIAL' + [] + [v_avg] + type = ElementAverageValue + variable = v + execute_on = 'TIMESTEP_END INITIAL' + [] + [timestep] + type = TimePostprocessor + outputs = 'none' + [] + [v_old] + type = ElementAverageValue + variable = v + execute_on = TIMESTEP_BEGIN + outputs = none + [] + [u_old] + type = ElementAverageValue + variable = u + execute_on = TIMESTEP_BEGIN + outputs = none + [] + [v_exact] + type = ParsedPostprocessor + pp_names = 'timestep v_old' + expression = 't := if(timestep > 5, 5, timestep); (t^2 - 9 * t) / 8' + [] + [u_exact] + type = ParsedPostprocessor + pp_names = 'u_old v_exact' + expression = 'u_old + v_exact' + [] +[] + +[Convergence] + [conv] + type = ReferenceResidualConvergence + nl_rel_tol = 1e-6 + [] +[] + +[Executioner] + type = Transient + petsc_options = '-snes_converged_reason' + petsc_options_iname = '-pc_type' + petsc_options_value = 'lu' + line_search = none + num_steps = 10 + nonlinear_convergence = conv + verbose = true +[] + +[Outputs] + csv = true + perf_graph = true +[] diff --git a/test/tests/convergence/reference_residual_convergence/reference_residual.i b/test/tests/convergence/reference_residual_convergence/reference_residual.i new file mode 100644 index 000000000000..4456d6885f8a --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/reference_residual.i @@ -0,0 +1,84 @@ +coef=1 + +[Mesh] + type = GeneratedMesh + dim = 1 + nx = 2 +[] + +[Problem] + # type = ReferenceResidualProblem + extra_tag_vectors = 'ref' +[] + +[Variables] + [u][] + [v][] +[] + +[Kernels] + [u_diff] + type = CoefDiffusion + variable = u + coef = ${coef} + [] + [u_rxn] + type = PReaction + variable = u + coefficient = ${coef} + power = 2 + [] + [u_f] + type = BodyForce + variable = u + value = ${coef} + [] + [v_diff] + type = Diffusion + variable = v + [] + [v_rxn] + type = PReaction + variable = v + coefficient = 1 + power = 2 + [] + [v_f] + type = BodyForce + variable = v + value = 1 + [] +[] + +[BCs] + [u] + type = RobinBC + boundary = 'left right' + coef = ${coef} + variable = u + extra_vector_tags = 'ref' + [] + [v] + type = RobinBC + boundary = 'left right' + coef = 1 + variable = v + extra_vector_tags = 'ref' + [] +[] + +[Convergence] + [conv] + type = ReferenceResidualConvergence + reference_vector = 'ref' + [] +[] + +[Executioner] + type = Steady + nonlinear_convergence = conv +[] + +[Outputs] + exodus = true +[] diff --git a/test/tests/convergence/reference_residual_convergence/tests b/test/tests/convergence/reference_residual_convergence/tests new file mode 100644 index 000000000000..2b65595f5ef3 --- /dev/null +++ b/test/tests/convergence/reference_residual_convergence/tests @@ -0,0 +1,161 @@ +[Tests] + issues = '#9151 #24128' + design = 'ReferenceResidualConvergence.md' + + [base] + type = Exodiff + input = 'reference_residual.i' + exodiff = 'reference_residual_out.e' + requirement = 'The system shall have the ability to base convergence on the comparison of ' + 'individual variables to reference quantities of those variables, using a convergence object.' + [] + [scaled_bad] + type = RunApp + input = 'reference_residual.i' + expect_out = 'Solve Did NOT Converge' + cli_args = 'coef=1e17 Outputs/exodus=false -pc_type svd -pc_svd_monitor -ksp_max_it 10' + requirement = 'The system shall fail to find a converged solution when basing convergence on ' + 'individual variable reference quantities with poor scaling, using a convergence object.' + [] + [scaled_good] + type = Exodiff + input = 'reference_residual.i' + cli_args = 'coef=1e17 Outputs/file_base=scaled Executioner/automatic_scaling=true ' + 'Executioner/verbose=true -pc_type svd -pc_svd_monitor -ksp_max_it 10' + exodiff = 'scaled.e' + requirement = 'The system shall have the ability to automatically scale a originally poorly ' + 'scaled problem and achieve convergence based on individual reference quantities, using a convergence object.' + petsc_version = '>=3.9.0' + max_parallel = 2 + [] + [converge_on] + type = RunApp + input = 'reference_residual.i' + expect_out = 'Solve Converged!' + cli_args = 'Convergence/conv/converge_on="u"' + requirement = 'The system shall have the ability to base system convergence on the convergence ' + 'of a subset of variables, using a convergence object.' + [] + [converge_on_group_error] + type = RunException + input = 'reference_residual.i' + expect_err = 'You added variable \'u\' to a group but excluded it from the convergence check. ' + 'This is not permitted.' + cli_args = 'Convergence/conv/converge_on="v" Convergence/conv/group_variables="u v"' + requirement = 'The system shall require all grouped variables to be included in the convergence ' + 'check, using a convergence object.' + [] + [wildcard] + type = RunApp + input = 'reference_residual.i' + expect_out = 'Solve Converged!' + cli_args = 'Convergence/conv/converge_on="?"' + requirement = 'The system shall support wildcards for choosing the variables to base convergence ' + 'on, using a convergence object.' + [] + [abs_ref] + type = CSVDiff + input = 'abs_ref.i' + csvdiff = 'abs_ref_out.csv' + requirement = 'The system shall have the ability to base convergence on the comparison of L2 normalization of the residual for ' + 'individual variables to the sum of the L2 normalization of the absolute value of the residual contributions ' + 'of those variables, using a convergence object.' + [] + [abs_ref_acceptable] + type = RunApp + input = 'abs_ref_acceptable.i' + expect_out = 'Converged due to ACCEPTABLE tolerances' + requirement = 'The system shall have the ability to base convergence on the comparison of L2 normalization of the residual for ' + 'individual variables to the sum of the L2 normalization of the absolute value of the residual contributions ' + 'of those variables using an acceptable tolerance past a given number of nonlinear iterations, using a convergence object.' + [] + [local_normalization] + type = CSVDiff + input = 'abs_ref.i' + csvdiff = 'abs_ref_local_out.csv' + cli_args = 'Convergence/conv/normalization_type=local_L2 Outputs/file_base=abs_ref_local_out' + requirement = 'The system shall have the ability to base convergence on the comparison of L2 normalization of the residual for ' + 'individual variables locally divided by the absolute value of the residual contributions ' + 'of those variables, using a convergence object.' + [] + [local_linf_normalization] + type = CSVDiff + input = 'abs_ref.i' + csvdiff = 'abs_ref_local_Linf_out.csv' + cli_args = 'Convergence/conv/normalization_type=local_Linf Outputs/file_base=abs_ref_local_Linf_out' + requirement = 'The system shall have the ability to base convergence on the comparison of Linf normalization of the residual for ' + 'individual variables locally divided by the absolute value of the residual contributions ' + 'of those variables, using a convergence object.' + [] + [linf_normalization] + type = CSVDiff + input = 'abs_ref.i' + csvdiff = 'abs_ref_Linf_out.csv' + cli_args = 'Convergence/conv/normalization_type=global_Linf Outputs/file_base=abs_ref_Linf_out' + requirement = 'The system shall have the ability to base convergence on the comparison of Linf normalization of the residual for ' + 'individual variables to the sum of the Linf normalization of the absolute value of the residual contributions ' + 'of those variables, using a convergence object.' + [] + [ad_abs_ref] + type = CSVDiff + input = 'ad_abs_ref.i' + csvdiff = 'ad_abs_ref_out.csv' + requirement = 'The system shall have the ability to base convergence on the comparison of L2 normalization of the residual for ' + 'individual variables to the sum of the L2 normalization of the absolute value of the residual contributions ' + 'of those variables using AD, using a convergence object.' + [] + [no_ref] + type = CSVDiff + input = 'no_ref.i' + csvdiff = 'no_ref_out.csv' + cli_args = 'Problem/extra_tag_vectors=absref' + requirement = 'The system shall have the ability to default to the traditional convergence checks if no ' + 'reference vector is provided in ReferenceResidualConvergence.' + [] + [zero_tolerance_ref] + type = RunException + input = no_ref.i + expect_err = 'Solve failed' + cli_args = 'Problem/extra_tag_vectors=absref Convergence/conv/reference_vector=absref Convergence/conv/zero_reference_residual_treatment=zero_tolerance' + requirement = 'The system shall treat convergence with a zero reference residual value as requiring zero residual value for convergence, using a convergence object.' + [] + [zero_rel_tolerance_ref] + type = CSVDiff + input = no_ref.i + csvdiff = 'zero_rel_tolerance_ref_out.csv' + cli_args = 'Problem/extra_tag_vectors=absref Convergence/conv/reference_vector=absref Convergence/conv/zero_reference_residual_treatment=relative_tolerance Convergence/conv/nl_rel_tol=1e-8 Outputs/file_base=zero_rel_tolerance_ref_out' + requirement = 'The system shall treat convergence with a zero reference residual value as converged if the residual is below the relative tolerance, using a convergence object.' + [] + + [error] + requirement = 'When using ReferenceResidualConvergence the system shall throw an error ' + [wrong_vector_tag_type] + type = RunException + input = abs_ref.i + cli_args = "Problem/extra_tag_solutions='absref' Problem/extra_tag_vectors=''" + expect_err = "is not a residual vector tag" + detail = "if the reference tag is not a residual vector tag." + [] + [no_tag] + type = RunException + input = abs_ref.i + cli_args = "Problem/extra_tag_vectors=''" + expect_err = "does not exist" + detail = "if the reference tag does not exist." + [] + [no_reference_vector] + type = RunException + input = no_ref.i + cli_args = "Convergence/conv/normalization_type=local_L2 Problem/extra_tag_vectors=absref" + expect_err = "If local norm is used, a reference_vector must be provided" + detail = "if the reference vector is not provided when using local normalization." + [] + [not_residual_tag] + type = RunException + input = no_ref.i + cli_args = "Problem/extra_tag_solutions=absref GlobalParams/absolute_value_vector_tags=absref" + expect_err = "is not a residual vector tag" + detail = "if the reference vector is provided is not a residual vector tag." + [] + [] +[] diff --git a/test/tests/minimal_app/tests b/test/tests/minimal_app/tests index 2b4ef60eb3a1..e42453a80106 100644 --- a/test/tests/minimal_app/tests +++ b/test/tests/minimal_app/tests @@ -6,7 +6,7 @@ # Make sure --minimal flag is working type = RunApp cli_args = '--minimal --list-constructed-objects' - expect_out = "FEProblem\nGeneratedMesh" + expect_out = "FEProblem\nDefaultNonlinearConvergence\nGeneratedMesh" input = '' input_switch = '' display_required = True