Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More expressions "improvements" #1764

Merged
merged 10 commits into from
Jul 22, 2019
Merged

More expressions "improvements" #1764

merged 10 commits into from
Jul 22, 2019

Conversation

bendudson
Copy link
Contributor

@bendudson bendudson commented Jul 4, 2019

Built on top of PR #1750 , this adds some features for making complex expressions in the input file easier to read / organise.

Local Scope

Adds syntax to define local scope. Underneath this inserts a FieldGenerator which modifies the Context object. One use is to simplify expressions using local variables, for efficiency and readability e.g. ensuring that a value is positive:

positive_value = H(expression) * expression

where the expression would be evaluated twice. This can be replaced by:

positive_value = [value = expression]( H({value}) * {value} )

This mechanism can also be used to implement simple functions

mycosh = 0.5 * (exp({arg}) + exp(-{arg}))

which can be called by defining a new Context:

result = [arg = x*2](mycosh)

(a sort of dynamic scope system)

so we could write:

floor = H({arg}) * {arg}
positive_value = [arg = expression](floor)

where function

Adds a where(test, gt0, lt0) function to FieldFactory. This enables more clear swiching than the H(x) Heaviside function. Like the C++ functions in BOUT++, one of two expressions is evaluated,
depending on the sign of the first expression.

Multi-line expressions

Enable multi-line expressions in input options, since long expressions can become hard to read in the options file if all on one line. This adopts the Python approach, where if there are unbalanced brackets ('()' or '[]') then the expression continues on the next line.

In the BOUT.inp file we can therefore write

value = (something
         + more
         - things)

or define complex expression using a Context scope:

result = [a = expression,
          b = something](
            {a} + {b})

sum function for looping

Add a sum function enables a simple kind of loop, but one which is guaranteed to finish. Iterates a given symbol from 0 to count-1, evaluating and summing a given expression. Example:

sum(i, 4, {i}^2)  # => 0^2 + 1^2 + 2^2 + 3^3

Recursive expressions

By default recursive expressions are not allowed in the input options. These can be enabled by setting input:max_recursion_depth != 0 e.g.:

[input]
max_recursion_depth = 10  # 0 = none, -1 = unlimited

By putting a limit on the depth we avoid the halting problem at least, and expressions should (eventually) terminate, preferably before they overflow the stack.

As is traditional, I present to you the Fibonacci sequence:

fib = where({n} - 2.5,
            [n={n}-1](fib) + [n={n}-2](fib),
            1)

so if n = 1 or 2 then fib = 1, but if n = 3 or above then the recursion fun begins.

And another ill-considered language is born. Sorry, I think it was inevitable.

Enables more clear swiching than the H(x) Heaviside function

Like the C++ functions in BOUT++, one of two expressions is evaluated,
depending on the sign of the first expression.
Allows the definition of new scopes with local variables,
by setting values in the `Context` object.

One use is to simplify expressions using local variables, for
efficiency and readability e.g. ensuring that a value is positive:
```
positive_value = H(expression) * expression
```
where the expression would be evaluated twice. This can be replaced by:
```
positive_value = [value = expression]( H({value}) * {value} )
```
This mechanism can also be used to implement simple functions
```
mycosh = 0.5 * (exp({arg}) + exp(-{arg}))
```
which can be called by defining a new Context:
```
result = [arg = x*2](mycosh)
```
(sort-of a dynamic scope system)

so we could write:
```
floor = H({arg}) * {arg}
positive_value = [arg = expression](floor)
```
Some code not recompiled previously, now updated.
Another test defining two variables in a `Context` object.
Long expressions can become hard to read in the options file.
This adopts the Python approach, where if there are unbalanced
brackets ('()' or '[]') then the expression continues on the next
line.

In the BOUT.inp file we can therefore write

```
value = (something
         + more
         - things)
```

or define complex expression using a Context scope:
```
result = [a = expression,
          b = something](
            {a} + {b})
```
// Ensure that brackets '()' and '[]'are balanced

// Count net number of opening and closing brackets
auto count_brackets = [](const string& input) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way of writing this, using std algorithms:

auto count_brackets = [](const std::string& input) {
  return std::count_if(begin(input), end(input), [](const char& ch) { return ch == '(' or ch == '['; }) 
       - std::count_if(begin(input), end(input), [](const char& ch) { return ch == ')' or ch == ']'; });
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks yes that would work. It traverses the string twice, but it is nicer in many ways. I'll change to this.

Enables a simple kind of loop, but one which is guaranteed to finish.
Iterates a given symbol from 0 to count-1, evaluating and summing
a given expression.
Using parseExpression captures the rest of the string,
rather than just the next expression. Instead call
parseParenExpr to stop when parentheses close.

Added tests to reproduce this.
By default recursive expressions are not allowed in the input options.
What is committed here adds an input option to allow recursion:
```
[input]
max_recursion_depth = 10  # 0 = none, -1 = unlimited
```
By putting a limit on the depth we avoid the halting problem at least,
and expressions should (eventually) terminate, preferably before they
overflow the stack.

As is traditional, I present to you the Fibonacci sequence:
```
fib = where({n} - 2.5,
            [n={n}-1](fib) + [n={n}-2](fib),
            1)
```
so if `n` = 1 or 2 then `fib` = 1, but if `n` = 3 or above then the
recursion fun begins.

And another ill-considered language is born.
`FieldFactory` reads `input:max_recursion_depth` as an int. `Options`
then creates a `FieldFactory` to parse the string to an int.

This change uses `std::stoi` to do the string conversion. This means
no fancy expressions in this parameter, but that should not be needed.
Documenting new features for expressions, and some examples of how to
use them. Some of the existing documentation was out of date from
earlier changes.
Slight efficiency improvement when repeatedly iterating over
@bendudson bendudson merged commit ec86a51 into ynorm2-simplify Jul 22, 2019
@bendudson bendudson deleted the more-expressions branch January 9, 2020 17:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants