Skip to content

Commit

Permalink
Added ParsedConvergence
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuahansel committed Jan 21, 2025
1 parent a835c80 commit 45389cc
Show file tree
Hide file tree
Showing 11 changed files with 518 additions and 0 deletions.
49 changes: 49 additions & 0 deletions framework/doc/content/source/convergence/ParsedConvergence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# ParsedConvergence

This [Convergence](Convergence/index.md) allows the user to specify arbitrary expressions
for convergence and divergence criteria. These expressions
([!param](/Convergence/ParsedConvergence/convergence_expression) and [!param](/Convergence/ParsedConvergence/divergence_expression))
may contain any of the following:

- `Convergence` objects
- [Functions](Functions/index.md)
- [Post-processors](Postprocessors/index.md)
- Constant values

The expressions are parsed using the [Function Parser syntax](http://warp.povusers.org/FunctionParser/fparser.html#functionsyntax).
The full library of mathematical operators is valid in the parsed
expression, but for convenience, we list some of the logical and comparison operators here:

| Syntax | Description |
| :- | :- |
| `()` | Parentheses for order of operations |
| `!A` | *NOT* `A` |
| `A & B` | `A` *AND* `B` |
| `A` I `B` | `A` *OR* `B` |
| `A = B` | `A` *EQUALS* `B` |
| `A != B` | `A` *DOES NOT EQUAL* `B` |
| `A >= B` | `A` *GREATER THAN OR EQUAL TO* `B` |

The expressions must evaluate to either 1 or 0, which correspond to `true` or `false`,
respectively; if the expression returns another value, an error results. Note
the following rules for the `Convergence` object values:

- For the convergence expression, `Convergence` objects evaluate to `true` if they
are `CONVERGED` and `false` otherwise (`ITERATING` or `DIVERGED`).
- For the divergence expression, `Convergence` objects evaluate to `true` if they
are `DIVERGED` and `false` otherwise (`ITERATING` or `CONVERGED`).

The divergence expression is optional. If omitted, divergence occurs if any of
the supplied `Convergence` objects return `DIVERGED`, e.g.,

```
divergence_expression = 'conv1 | conv2 | conv3'
```

if [!param](/Convergence/ParsedConvergence/symbol_values) contains `conv1`, `conv2`, and `conv3`.

!syntax parameters /Convergence/ParsedConvergence

!syntax inputs /Convergence/ParsedConvergence

!syntax children /Convergence/ParsedConvergence
98 changes: 98 additions & 0 deletions framework/include/convergence/ParsedConvergence.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//* 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 "FunctionParserUtils.h"

/**
* Evaluates convergence from a parsed expression.
*/
class ParsedConvergence : public Convergence, public FunctionParserUtils<false>
{
public:
static InputParameters validParams();

ParsedConvergence(const InputParameters & parameters);

virtual MooseConvergenceStatus checkConvergence(unsigned int iter) override;

virtual void initialSetup() override;

protected:
usingFunctionParserUtilsMembers(false);

/**
* Initializes symbols used in the parsed expression
*/
void initializeSymbols();
void initializePostprocessorSymbol(unsigned int i);
void initializeFunctionSymbol(unsigned int i);
void initializeConvergenceSymbol(unsigned int i);
void initializeConstantSymbol(unsigned int i);

/**
* Makes a parsed function
*
* @param[in] expression expression to parse
*/
SymFunctionPtr makeParsedFunction(const std::string & expression);

/**
* Updates non-constant symbol values
*
* @param[in] iter Iteration index
*/
void updateSymbolValues(unsigned int iter);
void updatePostprocessorSymbolValues();
void updateFunctionSymbolValues();
void updateConvergenceSymbolValues(unsigned int iter);

/**
* Converts a Real value to a bool. Error results if value is not 0 or 1.
*
* @param[in] value Real value to convert
* @param[in] param Name of the corresponding input parameter
*/
bool convertRealToBool(Real value, const std::string & param) const;

FEProblemBase & _fe_problem;

std::vector<std::string> _symbol_names;
std::vector<std::string> _symbol_values;

/// Expression to parse for convergence
const std::string _convergence_expression;
/// Expression to parse for divergence
const std::string _divergence_expression;

/// Parsed function for convergence
SymFunctionPtr _convergence_function;
/// Parsed function for divergence
SymFunctionPtr _divergence_function;

/// Convergence function parameters
std::vector<Real> _convergence_function_params;
/// Divergence function parameters
std::vector<Real> _divergence_function_params;

/// Post-processor values in the provided symbols
std::vector<const PostprocessorValue *> _pp_values;
std::vector<unsigned int> _pp_indices;

/// Functions in the provided symbols
std::vector<Function *> _functions;
std::vector<unsigned int> _function_indices;

/// Convergences in the provided symbols
std::vector<Convergence *> _convergences;
std::vector<std::string> _convergence_symbol_names;
std::vector<unsigned int> _convergence_indices;
};
227 changes: 227 additions & 0 deletions framework/src/convergence/ParsedConvergence.C
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
//* 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 "ParsedConvergence.h"
#include "MooseUtils.h"
#include "Function.h"

registerMooseObject("MooseApp", ParsedConvergence);

InputParameters
ParsedConvergence::validParams()
{
InputParameters params = Convergence::validParams();
params += FunctionParserUtils<false>::validParams();

params.addClassDescription("Evaluates convergence from a parsed expression.");

params.addRequiredCustomTypeParam<std::string>(
"convergence_expression", "FunctionExpression", "Expression to parse for convergence");
params.addCustomTypeParam<std::string>(
"divergence_expression", "FunctionExpression", "Expression to parse for divergence");
params.addParam<std::vector<std::string>>(
"symbol_names", {}, "Symbol names to use in the parsed expressions");
params.addParam<std::vector<std::string>>(
"symbol_values",
{},
"Values (Convergence names, Postprocessor names, Function names, and constants) "
"corresponding to each entry in 'symbol_names'");

return params;
}

ParsedConvergence::ParsedConvergence(const InputParameters & parameters)
: Convergence(parameters),
FunctionParserUtils<false>(parameters),
_fe_problem(*getCheckedPointerParam<FEProblemBase *>("_fe_problem_base")),
_symbol_names(getParam<std::vector<std::string>>("symbol_names")),
_symbol_values(getParam<std::vector<std::string>>("symbol_values")),
_convergence_function_params(_symbol_names.size(), 0.0),
_divergence_function_params(_symbol_names.size(), 0.0)
{
if (_symbol_names.size() != _symbol_values.size())
mooseError("The parameters 'symbol_names' and 'symbol_values' must have the same size.");
}

void
ParsedConvergence::initialSetup()
{
Convergence::initialSetup();

initializeSymbols();

const auto convergence_expression = getParam<std::string>("convergence_expression");
_convergence_function = makeParsedFunction(convergence_expression);

const auto divergence_expression = isParamValid("divergence_expression")
? getParam<std::string>("divergence_expression")
: MooseUtils::join(_convergence_symbol_names, "|");
_divergence_function = makeParsedFunction(divergence_expression);
}

void
ParsedConvergence::initializeSymbols()
{
for (const auto i : index_range(_symbol_values))
{
ReporterName reporter_name(_symbol_values[i], "value");
if (_fe_problem.getReporterData().hasReporterValue<PostprocessorValue>(reporter_name))
initializePostprocessorSymbol(i);
else if (_fe_problem.hasFunction(_symbol_values[i]))
initializeFunctionSymbol(i);
else if (_fe_problem.hasConvergence(_symbol_values[i]))
initializeConvergenceSymbol(i);
else
initializeConstantSymbol(i);
}
}

void
ParsedConvergence::initializePostprocessorSymbol(unsigned int i)
{
const PostprocessorValue & pp_value = _fe_problem.getPostprocessorValueByName(_symbol_values[i]);
_pp_values.push_back(&pp_value);
_pp_indices.push_back(i);
}

void
ParsedConvergence::initializeFunctionSymbol(unsigned int i)
{
Function & function = _fe_problem.getFunction(_symbol_values[i], _tid);
_functions.push_back(&function);
_function_indices.push_back(i);
}

void
ParsedConvergence::initializeConvergenceSymbol(unsigned int i)
{
Convergence & convergence = _fe_problem.getConvergence(_symbol_values[i], _tid);
_convergences.push_back(&convergence);
_convergence_symbol_names.push_back(_symbol_names[i]);
_convergence_indices.push_back(i);
}

void
ParsedConvergence::initializeConstantSymbol(unsigned int i)
{
try
{
const Real value = MooseUtils::convert<Real>(_symbol_values[i], true);
_convergence_function_params[i] = value;
_divergence_function_params[i] = value;
}
catch (const std::invalid_argument & e)
{
mooseError(
"The 'symbol_values' entry '",
_symbol_values[i],
"' is not a constant value or the name of a Convergence, Postprocessor, or Function.",
e.what());
}
}

ParsedConvergence::SymFunctionPtr
ParsedConvergence::makeParsedFunction(const std::string & expression)
{
auto sym_function = std::make_shared<SymFunction>();

setParserFeatureFlags(sym_function);

// Add constants
sym_function->AddConstant("pi", std::acos(Real(-1)));
sym_function->AddConstant("e", std::exp(Real(1)));

// Parse the expression
const auto symbols_str = Moose::stringify(_symbol_names);
if (sym_function->Parse(expression, symbols_str) >= 0)
mooseError("The expression\n '",
expression,
"'\nwith symbols\n '",
symbols_str,
"'\ncould not be parsed:\n",
sym_function->ErrorMsg());

// Optimize the parsed function
functionsOptimize(sym_function);

return sym_function;
}

Convergence::MooseConvergenceStatus
ParsedConvergence::checkConvergence(unsigned int iter)
{
updateSymbolValues(iter);

const Real converged_real = evaluate(_convergence_function, _convergence_function_params, name());
const Real diverged_real = evaluate(_divergence_function, _divergence_function_params, name());

if (convertRealToBool(diverged_real, "divergence_expression"))
return MooseConvergenceStatus::DIVERGED;
else if (convertRealToBool(converged_real, "convergence_expression"))
return MooseConvergenceStatus::CONVERGED;
else
return MooseConvergenceStatus::ITERATING;
}

void
ParsedConvergence::updateSymbolValues(unsigned int iter)
{
updatePostprocessorSymbolValues();
updateFunctionSymbolValues();
updateConvergenceSymbolValues(iter);
}

void
ParsedConvergence::updatePostprocessorSymbolValues()
{
for (const auto i : index_range(_pp_indices))
{
_convergence_function_params[_pp_indices[i]] = (*_pp_values[i]);
_divergence_function_params[_pp_indices[i]] = (*_pp_values[i]);
}
}

void
ParsedConvergence::updateFunctionSymbolValues()
{
for (const auto i : index_range(_function_indices))
{
const Real function_value = _functions[i]->value(_t, Point(0, 0, 0));
_convergence_function_params[_function_indices[i]] = function_value;
_divergence_function_params[_function_indices[i]] = function_value;
}
}

void
ParsedConvergence::updateConvergenceSymbolValues(unsigned int iter)
{
for (const auto i : index_range(_convergence_indices))
{
const auto status = _convergences[i]->checkConvergence(iter);
_convergence_function_params[_convergence_indices[i]] =
status == MooseConvergenceStatus::CONVERGED;
_divergence_function_params[_convergence_indices[i]] =
status == MooseConvergenceStatus::DIVERGED;
}
}

bool
ParsedConvergence::convertRealToBool(Real value, const std::string & param) const
{
if (MooseUtils::absoluteFuzzyEqual(value, 1.0))
return true;
else if (MooseUtils::absoluteFuzzyEqual(value, 0.0))
return false;
else
mooseError("The expression parameter '",
param,
"' evaluated to the value ",
value,
", but it must only evaluate to either 0 or 1.");
}
7 changes: 7 additions & 0 deletions modules/doc/content/newsletter/2025/2025_01.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ for a complete description of all MOOSE changes.

## MOOSE Improvements

### Added ParsedConvergence

[/ParsedConvergence.md] was added, which allows the user to specify arbitrary
convergence and divergence criteria. The parsed expression may include other
[Convergence](Convergence/index.md) objects, [Functions](Functions/index.md),
[Post-processors](Postprocessors/index.md), and constant values.

## MOOSE Modules Changes

## libMesh-level Changes
Expand Down
Loading

0 comments on commit 45389cc

Please sign in to comment.