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

feat: create Expression type to store operators with expression and parse_expression to have robust parsing #73

Merged
merged 77 commits into from
Jun 4, 2024

Conversation

MilesCranmer
Copy link
Member

@MilesCranmer MilesCranmer commented Apr 28, 2024

Expressions

This creates the Expression and AbstractExpression type for attaching operators and potentially other metadata to the expression itself.

This will likely be most useful as user-facing type to return self-contained expressions. I think it can also be useful for expression searches that attach other types of metadata.

This means you can do things like

expression(X)
# ^ Rather than `tree(X, operators)`

println(expression)
# ^ Rather than `print_tree(expression; operators, variable_names)`

without needing to provide the operators or variable names – they are stored inside the Expression struct.

Previously you needed to keep track of these yourself which is a bit of a pain if you aren't deeply knowledgeable about how everything works.

Parsing

In addition to this, I also add @parse_expression macro. This is a safer and easier alternative to operator overloading for creating Node types.

Basically it works as follows:

using DynamicExpressions

my_op(x) = cos(x^2)

operators = OperatorEnum(binary_operators=[+, -, *, /], unary_operators=[my_op])
variable_names = [, ]  # Or strings

expr = @parse_expression(
    my_op(2.3 * α - β) / (0.5 * β * β),  # Pass an expression that will be parsed into an equivalent `Node`
    operators = operators,
    variable_names = variable_names,
)

X = randn(2, 100)

expr(X)
# ^ This is now valid, because `expr.operators` points to the operator enum.
# Also valid: `eval_tree_array(expr, X)`

println(expr)
# ^ Has the `α` and `β` despite everything being stored as the lightweight `Node` type!

expr'(X)
# ^ Forward-mode gradient with respect to inputs

You can also optionally provide a node_type argument to specify e.g., use of a GraphNode instead of a Node.

This is much nicer because it means you can have multiple sets of operator enums, and generate expressions without worrying about overwriting the global mapping from function to index.


TODO:

  • Rewrite Expression to allow anything being set for the operators field (including nothing). And rewrite the field to be settings so its clear its not specifically operators.
  • Allow variable_names to be nothing.
  • Rewrite options function to allow passing both the expression (in case it contains its own settings), AND an options variable that by default is nothing. This would let you work with an expression that does not refer to its own options.
  • Similar^ interface for variable_names.
  • Create other field in Expression for referring to arbitrary metadata.
  • Test a mechanism for MultiExpression and ParametricExpression

Tagging in case of interest @avik-pal @AlCap23 @rikhuijzer @Moelf

@coveralls
Copy link

coveralls commented Apr 28, 2024

Pull Request Test Coverage Report for Build 9182557303

Details

  • 234 of 239 (97.91%) changed or added relevant lines in 6 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.6%) to 95.429%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/Expression.jl 88 89 98.88%
src/Parse.jl 107 111 96.4%
Totals Coverage Status
Change from base Build 8876266483: 0.6%
Covered Lines: 1858
Relevant Lines: 1947

💛 - Coveralls

@daloic
Copy link

daloic commented Apr 28, 2024

With my very limited Julia knowledge, this looks like solving the issues I have and probably allowing the end user to later easily create a backbone expression from a parsed formula to feed the search with a seed. I like it.

This comment was marked as resolved.

Project.toml Outdated Show resolved Hide resolved
@rikhuijzer
Copy link
Contributor

Tagging in case of interest @avik-pal @AlCap23 @rikhuijzer @Moelf

Thanks indeed for the reminder that I should try out this package. I do have some use-cases where it would be useful for

@MilesCranmer
Copy link
Member Author

I'm also thinking about just ditching the @parse_expression altogether in favor of a parse_expression(::Expr, ...) function. It doesn't actually need to have a block of code as input so not sure there is need.

@MilesCranmer MilesCranmer merged commit a55f966 into master Jun 4, 2024
9 checks passed
@MilesCranmer MilesCranmer deleted the expression-type branch June 4, 2024 00:00
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.

4 participants