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

Expression utility class for C++ solvers #582

Merged
merged 18 commits into from
Aug 24, 2021

Conversation

jtcooper10
Copy link
Collaborator

@jtcooper10 jtcooper10 commented Jul 15, 2021

Upcoming SBML features (function definitions and events, especially) will require more advanced conversions from Python expressions to C++ expressions, in particular for language constructs like boolean operators. This utility class is used to convert a valid Python expression into a valid C++ expression, within reason.

Expression Class

Create an expression context with namespace (dictionary of mappings, the keys of which are expected to be valid names in the namespace) and blacklist (a list of operators which are not allowed). Once created, the Expression#getexpr_cpp and Expression#getexpr_python can be called to extract C++ and Python expressions, respectively. If Expression is constructed with sanitize=True, then the identifiers in the expression will be substituted with the identifiers provided in the namespace dict.

Additionally, the Expression#validate method may be used to verify that the provided expression is, indeed, a valid Python expression, given the provided namespace and blacklist. A standard SyntaxError will be raised if the expression cannot be parsed.

from gillespy2.solvers.cpp.build.expression import Expression

expr = Expression(namespace={ "x": "S1", "y": "S2" }, blacklist=["="])
expr.validate("x + y")
> True
expr.validate("x = y")
> False
expr.validate("y + z")
> False
try:
    expr.validate("x++")
except SyntaxError:
    print("Syntax Error")
> Syntax Error
expr.getexpr_cpp("x > 0 and y < 10")
> (x>0) && (y<10)

Intended Use

The template_gen methods have been updated to use the new Expression class for sanitizing and validating expressions. This allows us to inform the user of syntax errors prior to the solver's compile step. The user will see a friendlier Python error message, instead of a massive C++ compiler exception.

Additionally, this will be used to create a variety of C++ expressions to be used for upcoming SBML features, many of which require different syntax for Python and C++ solvers. For example, the boolean expression x > 0 and y**2 < 10 is valid in Python, but must be converted to x > 0 && pow(y, 2) < 10 for C.

Supported Operators

Currently, only basic operators are supported: arithmetic operators, boolean operators (and and or), and comparison operators. It doesn't seem like there's much point in implementing more than that at the moment, as this will likely be limited to propensity functions and basic SBML features.

However, if for any reason additional operations are to be supported, they can be added simply by implementing the parser logic using that operator's corresponding visit_* method (such as visit_Add for the + operator), and adding that operator to the operator_map along with its corresponding AST class.

Note that the use of unsupported operators is currently undefined behavior, likely resulting in a failure to compile or bizarre output.

jtcooper10 and others added 10 commits July 13, 2021 11:33
- Accepts a Python statement string
- Contains static class `ValidationVisitor` to validate parsed expression
- Static method `map_operator` for mapping strings to actual AST operators
- Modify `with_*` methods to create a duplicate expression rather than mutating
- Updated `validate` method for validating any expression against the given state
Processes the parsed AST into a Python string, accessible with `.getexpr()`.
- New `PythonConverter` class, member of `Expression` class
- Base wrapper with `Expression#getexpr_python` method
- Adds `**` and `^` as mapped operators
- Adds `convert` method to convert Xor to `**`
- C++ transformer for converting Python-exclusive operators to C++ function calls
- Move converter classes out of base `Expression` class
- Implement base `Converter` class for both Python and C++ converters to inherit from
- Implement logical and/or expressions
- Add comparison operators to visitor class
- Call `get_str()` from `Expression` class for C++ expressions
- Performs substitution on statement strings if requested
- Additional operators are now supported
- Remove call to `Reaction#sanitized_propensity_function`
- Remove circular import caused by `import log`
- Add `Num`, `Str`, `Bytes`, `NameConstant`, and `Ellipsis` visitor methods
  - These are deprecated in favor of `Constant` in 3.8, but are required in 3.7
- Remove random print statement
…o Python.

- Test case added to `test_c_solvers.py`
- Converts C++ strings with complex operations, compiles to C, asserts the results
- Could use additional tests for boolean operators, blacklists, etc.
- Test suite now includes `comparisons` list for asserting booleans and comparators
- FIX: bug where multiple boolean operators would be overridden by the outermost one
  - Discovered because of the above boolean expression tests!
- Added `ExpressionResults` class, acts as "struct" for validation
  - Contains any invalid names/operators found
- Updated `blacklist` to accept both lists and dict
  - Stored as a dictionary mapping AST operators to their strings
  - Lets us print the actual operator symbol when logging
@jtcooper10 jtcooper10 marked this pull request as ready for review July 19, 2021 21:28
- Allows for unary subtract, for constants like `-1.0`
@jtcooper10 jtcooper10 mentioned this pull request Jul 29, 2021
Copy link
Collaborator

@ethangreen-dev ethangreen-dev left a comment

Choose a reason for hiding this comment

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

Looks great!

@briandrawert briandrawert added this to the 1.6.3 milestone Aug 17, 2021
@briandrawert briandrawert merged commit 0b061a5 into develop Aug 24, 2021
@briandrawert briandrawert deleted the feature/expression-validation branch August 24, 2021 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants