Skip to content

Commit

Permalink
ParmParse: Math expression support (AMReX-Codes#4032)
Browse files Browse the repository at this point in the history
ParmParse: Math expression support

Support math expression for integers and floating point numbers in
ParmParse. For example,

    n_cell = 128
    amrex.n_cell = n_cell*2 8 16**2

becomes

    n_cell = 128
    amrex.n_cell = 256 8 256

Note that if the expression contains another variable, it will be looked up by ParmParse.

ParmParse's constructor now accepts an optional second argument,
`parser_prefix`. When a variable in a math expression is being looked
up, it
will first try to find it by using the exact name of the variable. If
this
attempt fails and the `ParmParse` object has a non-empty
`parser_prefix`, it
will try again, this time looking up the variable by prefixing its name
with
`parser_prefix` followed by a `.`. For example,

    physical_constants.c = 3.e8
    amrex.pi = 3.14
    amrex.foo = sin(amrex.pi/2)*c**2

will give `foo = 8.999997146e+16` if we do

    amrex::ParmParse pp("amrex", "physical_constants");
    double foo;
    pp.get("foo", foo);
  • Loading branch information
WeiqunZhang authored Jul 26, 2024
1 parent 11d31e5 commit fd1633e
Show file tree
Hide file tree
Showing 3 changed files with 479 additions and 85 deletions.
159 changes: 150 additions & 9 deletions Docs/sphinx_documentation/source/Basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -224,17 +224,17 @@ file is a series of
definitions in the form of ``prefix.name = value value ....`` For each line,
text after # are comments. Here is an example inputs file.

.. highlight:: python
.. highlight:: python

::
::

nsteps = 100 # integer
nsteps = 1000 # nsteps appears a second time
dt = 0.03 # floating point number
ncells = 128 64 32 # a list of 3 ints
xrange = -0.5 0.5 # a list of 2 reals
title = "Three Kingdoms" # a string
hydro.cfl = 0.8 # with prefix, hydro
nsteps = 100 # integer
nsteps = 1000 # nsteps appears a second time
dt = 0.03 # floating point number
ncells = 128 64 32 # a list of 3 ints
xrange = -0.5 0.5 # a list of 2 reals
title = "Three Kingdoms" # a string
hydro.cfl = 0.8 # with prefix, hydro

The following code shows how to use :cpp:`ParmParse` to get/query the values.

Expand Down Expand Up @@ -275,6 +275,147 @@ by default returns the last one. The difference between :cpp:`query` and
get the value, whereas :cpp:`query` returns an error code without generating a
runtime error that will abort the run.

Math Expressions
----------------

:cpp:`ParmParse` supports math expressions for integers and floating point
numbers. For example,

.. highlight:: python

::

# three numbers. whitespaces inside `""` are okay.
f = 3+4 99 "5 + 6"

# two numbers. `\` is for continuation
g = 3.1+4.1 \
5.0+6.6

# two numbers unless using [query|get]WithParser
w = 1 -2

my_constants.alpha = 5.
amrex.c = c

# must use [query|get]WithParser
amrex.foo = sin( pi/2 ) + alpha + -amrex.c**2/c^2

# either [query|get] or [query|get]WithParser is okay
amrex.bar = sin(pi/2)+alpha+-amrex.c**2/c^2

geom.prob_lo = 2*sin(pi/4)/sqrt(2) sin(pi/2)+cos(pi/2) -(sin(pi*3/2)+cos(pi*3/2))

# three numbers. `\` is for continuation
geom.prob_hi = "2*sin(pi/4)/sqrt(2)" \
"sin(pi/2) + cos(pi/2)" \
-(sin(pi*3/2)+cos(pi*3/2))

can be processed by

.. highlight:: c++

::

{
ParmParse::SetParserPrefix("physical_constants");
ParmParse pp("physical_constants");
pp.add("c", 299792458.);
pp.add("pi", 3.14159265358979323846);
}
{
ParmParse pp;

double f0 = -1;
pp.query("f", f0);
std::cout << " double f = " << f0 << '\n';

std::vector<int> f;
pp.queryarr("f", f);
std::cout << " int f[3] = {" << f[0] << ", " << f[1] << ", "
<< f[2] << "}\n";

std::vector<double> g;
pp.queryarr("g", g);
std::cout << " double g[] = " << g[0] << " " << g[1] << '\n';

double w;
pp.query("w", w);
std::cout << " w = " << w << " with query\n";
pp.queryWithParser("w", w);
std::cout << " w = " << w << " with queryParser\n";
}
{
ParmParse pp("amrex", "my_constants");
double foo = -1, bar;
pp.getWithParser("foo", foo);
pp.get("bar", bar);
std::cout << " foo = " << foo << ", bar = " << bar << '\n';
}
{
ParmParse pp;
std::array<double,3> prob_lo, prob_hi;
pp.get("geom.prob_lo", prob_lo);
pp.get("geom.prob_hi", prob_hi);
std::cout << " double prob_lo[] = {" << prob_lo[0] << ", "
<< prob_lo[1] << ", " << prob_lo[2] << "}\n"
<< " double prob_hi[] = {" << prob_hi[0] << ", "
<< prob_hi[1] << ", " << prob_hi[2] << "}\n";
}

The results will be

.. highlight:: console

::

double f = 7
int f[3] = {7, 99, 11}
double g[] = 7.2 11.6
w = 1 with query
w = -1 with queryParser
foo = 5, bar = 5
double prob_lo[] = {1, 1, 1}
double prob_hi[] = {1, 1, 1}

Note that the empty spaces are significant for math expressions unless they
are inside a pair of ``"`` or explicitly parsed by
:cpp:`ParmParse::queryWithParser` or :cpp:`ParmParse::getWithParser`. If the
expression contains another variable, it will be looked up by
:cpp:`ParmParse`. :cpp:`ParmParse`'s constructor accepts an optional second
argument, ``parser_prefix``. When a variable in a math expression is being
looked up, it will first try to find it by using the exact name of the
variable. If this attempt fails and the :cpp:`ParmParse` object has a
non-empty non-static member ``parser_prefix``, it will try again, this time
looking up the variable by prefixing its name with the value of
``parser_prefix`` followed by a ``.``. If this attempt also fails and the
:cpp:`ParmParse` class has a non-empty static member ``ParserPrefix`` (which
can be set by :cpp:`ParmParse::SetParserPrefix`), it will try again, this
time looking up the variable by prefixing its name with the value of
``ParserPrefix`` followed by a ``.``.

The variables in :cpp:`ParmParse` math expressions are not evaluated until
they are referenced. If a variable is defined multiple times, the last
occurrence will override previous ones even if it appears after the variable
has been referenced. This behavior is demonstrated in the following example.

.. highlight:: python

::

foo.a = 1
foo.b = foo.a
foo.a = 2

will become

.. highlight:: python

::

foo.a = 2
foo.b = 2

Overriding Parameters with Command-Line Arguments
-------------------------------------------------

Expand Down
90 changes: 86 additions & 4 deletions Src/Base/AMReX_ParmParse.H
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include <AMReX_BLassert.H>
#include <AMReX_INT.H>
#include <AMReX_IParser.H>
#include <AMReX_Parser.H>
#include <AMReX_TypeTraits.H>

#include <array>
Expand Down Expand Up @@ -268,18 +270,31 @@ using IntVect = IntVectND<AMREX_SPACEDIM>;
* t = 1.5
* #endif
*
* Math expression is supported for integers and reals. For example
*
* n_cell = 128
* amrex.n_cell = n_cell*2 8 16**2
*
* becomes
*
* n_cell = 128
* amrex.n_cell = 256 8 256
*
* More details can be found at https://amrex-codes.github.io/amrex/docs_html/Basics.html#parmparse
*/
class ParmParse
{
public:
enum { LAST = -1, FIRST = 0, ALL = -1 };
/**
* \brief Construct an additional ParmParse object sharing the same
* internal table as any other such objects in existence. If
* prefix is specified, load this string as the code prefix
* for this particular ParmParse object.
* internal table as any other such objects in existence. If prefix is
* specified, load this string as the code prefix for this particular
* ParmParse object. If parser_prefix is specified, it will be used as
* prefixed in math expression evaluations.
*/
explicit ParmParse (std::string prefix = std::string());
explicit ParmParse (std::string prefix = std::string(),
std::string parser_prefix = std::string());

//! Returns true if name is in table.
[[nodiscard]] bool contains (const char* name) const;
Expand Down Expand Up @@ -997,9 +1012,70 @@ public:
return exist;
}

/**
* \brief Query with Parser. If `name` is found, this uses amrex::Parser
* to parse the entire list of empty space separated values as a single
* scalar. The return value indicates whether it's found.
*/
int queryWithParser (const char* name, int& ref) const;
int queryWithParser (const char* name, long& ref) const;
int queryWithParser (const char* name, long long& ref) const;
int queryWithParser (const char* name, float& ref) const;
int queryWithParser (const char* name, double& ref) const;

/**
* \brief Query with Parser. If `name` is found, this uses amrex::Parser
* to parse the entire list of empty space separated values as a single
* scalar. If not, the value in `ref` will be added to the ParmParse
* database. The return value indicates whether it's found.
*/
template <typename T, std::enable_if_t<std::is_same_v<T,int> ||
std::is_same_v<T,long> ||
std::is_same_v<T,long long> ||
std::is_same_v<T,float> ||
std::is_same_v<T,double>,int> = 0>
int queryAddWithParser (const char* name, T& ref) const
{
int exist = this->queryWithParser(name, ref);
if (!exist) {
this->add(name, ref);
}
return exist;
}

/**
* \brief Get with Parser. If `name` is found, this uses amrex::Parser
* to parse the entire list of empty space separated values as a single
* scalar. If not, it's a runtime error.
*/
template <typename T, std::enable_if_t<std::is_same_v<T,int> ||
std::is_same_v<T,long> ||
std::is_same_v<T,long long> ||
std::is_same_v<T,float> ||
std::is_same_v<T,double>,int> = 0>
void getWithParser (const char* name, T& ref) const
{
int exist = this->queryWithParser(name, ref);
if (!exist) {
amrex::Error(std::string("ParmParse::getWithParser: failed to get ")+name);
}
}

//! Remove given name from the table.
int remove (const char* name);

//! Make Parser using given string `func` as function body and `vars` as
//! variable names. Constants known to ParmParse will be set. It's a
//! runtime error, if there are unknown symbols in `func`.
[[nodiscard]] Parser makeParser (std::string const& func,
Vector<std::string> const& vars) const;

//! Make IParser using given string `func` as function body and `vars`
//! as variable names. Constants known to ParmParse will be set. It's a
//! runtime error, if there are unknown symbols in `func`.
[[nodiscard]] IParser makeIParser (std::string const& func,
Vector<std::string> const& vars) const;

/**
* \brief Construct an initial ParmParse object from the argc and argv
* passed in to main(). An error will be signalled if another
Expand All @@ -1014,6 +1090,9 @@ public:
*/
static void Finalize ();

//! Set prefix used by math expression Parser
static void SetParserPrefix (std::string a_prefix);

static int Verbose ();
static void SetVerbose (int v);

Expand Down Expand Up @@ -1049,11 +1128,14 @@ public:
//! keyword for files to load
static std::string const FileKeyword;

static std::string ParserPrefix;

protected:

[[nodiscard]] std::string prefixedName (const std::string_view& str) const;

std::string m_prefix; // Prefix used in keyword search
std::string m_parser_prefix; // Prefix used by Parser
Table* m_table;
};

Expand Down
Loading

0 comments on commit fd1633e

Please sign in to comment.