DynamicExpressions
Ridiculously fast symbolic expressions
Science Score: 44.0%
This score indicates how likely this project is to be science-related based on various indicators:
-
✓CITATION.cff file
Found CITATION.cff file -
✓codemeta.json file
Found codemeta.json file -
✓.zenodo.json file
Found .zenodo.json file -
○DOI references
-
○Academic publication links
-
○Academic email domains
-
○Institutional organization owner
-
○JOSS paper metadata
-
○Scientific vocabulary similarity
Low similarity (11.2%) to scientific vocabulary
Keywords
Repository
Ridiculously fast symbolic expressions
Basic Info
- Host: GitHub
- Owner: SymbolicML
- License: apache-2.0
- Language: Julia
- Default Branch: master
- Homepage: https://ai.damtp.cam.ac.uk/dynamicexpressions
- Size: 3.79 MB
Statistics
- Stars: 121
- Watchers: 2
- Forks: 16
- Open Issues: 20
- Releases: 72
Topics
Metadata Files
README.md
*Ridiculously fast dynamic expressions.*
[](https://symbolicml.org/DynamicExpressions.jl/dev) [](https://github.com/SymbolicML/DynamicExpressions.jl/actions/workflows/CI.yml) [](https://coveralls.io/github/SymbolicML/DynamicExpressions.jl?branch=master) [](https://github.com/JuliaTesting/Aqua.jl) [](https://github.com/MilesCranmer/DispatchDoctor.jl)
DynamicExpressions.jl is the backbone of [SymbolicRegression.jl](https://github.com/MilesCranmer/SymbolicRegression.jl) and
[PySR](https://github.com/MilesCranmer/PySR).
Summary
A dynamic expression is a snippet of code that can change throughout runtime - compilation is not possible! DynamicExpressions.jl does the following: 1. Defines an enum over user-specified operators. 2. Using this enum, it defines a very lightweight and type-stable data structure for arbitrary expressions. 3. It then generates specialized evaluation kernels for the space of potential operators. 4. It also generates kernels for the first-order derivatives, using Zygote.jl. 5. DynamicExpressions.jl can also operate on arbitrary other types (vectors, tensors, symbols, strings, or even unions) - see last part below. 6. It also has import and export functionality with SymbolicUtils.jl.
Example
```julia using DynamicExpressions
operators = OperatorEnum(1 => (cos,), 2 => (+, -, *)) variable_names = ["x1", "x2"]
x1 = Expression(Node{Float64}(feature=1); operators, variablenames) x2 = Expression(Node{Float64}(feature=2); operators, variablenames)
expression = x1 * cos(x2 - 3.2)
X = randn(Float64, 2, 100); expression(X) # 100-element Vector{Float64} ```
Speed
First, what happens if we naively use Julia symbols to define and then evaluate this expression?
```julia @btime eval(:(X[1, :] .* cos.(X[2, :] .- 3.2)))
117,000 ns
```
This is quite slow, meaning it will be hard to quickly search over the space of expressions. Let's see how DynamicExpressions.jl compares:
```julia @btime expression(X)
607 ns
```
Much faster! And we didn't even need to compile it. (Internally, this is calling eval_tree_array(expression, X)).
If we change expression dynamically with a random number generator, it will have the same performance:
```julia @btime ex(X) setup=(ex = copy(expression); ex.tree.op = rand(1:3) #= random operator in [+, -, *] =#)
640 ns
```
Now, let's see the performance if we had hard-coded these expressions:
```julia f(X) = X[1, :] .* cos.(X[2, :] .- 3.2) @btime f(X)
629 ns
```
So, our dynamic expression evaluation is about the same (or even a bit faster) as evaluating a basic hard-coded expression! Let's see if we can optimize the speed of the hard-coded version:
```julia foptimized(X) = begin y = Vector{Float64}(undef, 100) @inbounds @simd for i=1:100 y[i] = X[1, i] * cos(X[2, i] - 3.2) end y end @btime foptimized(X)
526 ns
```
The DynamicExpressions.jl version is only 25% slower than one which has been optimized by hand into a single SIMD kernel! Not bad at all.
More importantly: we can change expression throughout runtime, and expect the same performance. This makes this data structure ideal for symbolic regression and other evaluation-based searches over expression trees.
Derivatives
We can also compute gradients with the same speed:
```julia using Zygote # trigger extension
operators = OperatorEnum(1 => (cos,), 2 => (+, -, *)) variablenames = ["x1", "x2"] x1, x2 = (Expression(Node{Float64}(feature=i); operators, variablenames) for i in 1:2)
expression = x1 * cos(x2 - 3.2) ```
We can take the gradient with respect to inputs with simply the ' character:
julia
grad = expression'(X)
This is quite fast:
```julia @btime expression'(X)
2333 ns
```
and again, we can change this expression at runtime, without loss in performance!
```julia @btime ex'(X) setup=(ex = copy(expression); ex.tree.op = rand(1:3))
2333 ns
```
Internally, this is calling the eval_grad_tree_array function, which performs forward-mode automatic differentiation on the expression tree with Zygote-compiled kernels. We can also compute the derivative with respect to constants:
julia
result, grad, did_finish = eval_grad_tree_array(expression, X; variable=false)
Generic types
Does this work for only scalar operators on real numbers, or will it work for
MyCrazyType?
I'm so glad you asked. DynamicExpressions.jl actually will work for arbitrary types! However, to work on operators other than real scalars, you need to use the GenericOperatorEnum <: AbstractOperatorEnum instead of the normal OperatorEnum. Let's try it with strings!
julia
_x1 = Node{String}(; feature=1)
This node, will be used to index input data (whatever it may be) with either data[feature] (1D abstract arrays) or selectdim(data, 1, feature) (ND abstract arrays). Let's now define some operators to use:
```julia using DynamicExpressions: @declareexpressionoperator
mystringfunc(x::String) = "ello $x" @declareexpressionoperator(mystringfunc, 1)
operators = GenericOperatorEnum(1 => (mystringfunc,), 2 => (*,))
x1 = Expression(x1; operators, variablenames) ```
Now, let's create an expression:
```julia expression = "H" * mystringfunc(x1)
^ (H * my_string_func(x1))
expression(["World!", "Me?"])
Hello World!
```
So indeed it works for arbitrary types. It is a bit slower due to the potential for type instability, but it's not too bad:
```julia @btime expression(["Hello", "Me?"])
103.105 ns (4 allocations: 144 bytes)
```
Tensors
Does this work for tensors, or even unions of scalars and tensors?
Also yes! Let's see:
```julia using DynamicExpressions using DynamicExpressions: @declareexpressionoperator
T = Union{Float64,Vector{Float64}}
Some operators on tensors (multiple dispatch can be used for different behavior!)
vecadd(x, y) = x .+ y vecsquare(x) = x .* x
Enable these operators for DynamicExpressions.jl:
@declareexpressionoperator(vecadd, 2) @declareexpressionoperator(vecsquare, 1)
Set up an operator enum:
operators = GenericOperatorEnum(1 => (vecsquare,), 2 => (vecadd,))
Construct the expression:
variablenames = ["x1"] c1 = Expression(Node{T}(; val=0.0); operators, variablenames) # Scalar constant c2 = Expression(Node{T}(; val=[1.0, 2.0, 3.0]); operators, variablenames) # Vector constant x1 = Expression(Node{T}(; feature=1); operators, variablenames)
expression = vecadd(vecadd(vec_square(x1), c2), c1)
X = [[-1.0, 5.2, 0.1], [0.0, 0.0, 0.0]]
Evaluate!
expression(X) # [2.0, 29.04, 3.01] ```
Note that if an operator is not defined for the particular input, nothing will be returned instead.
This is all still pretty fast, too:
```julia @btime expression(X)
461.086 ns (13 allocations: 448 bytes)
@btime eval(:(vecadd(vecadd(vec_square(X[1]), [1.0, 2.0, 3.0]), 0.0)))
115,000 ns
```
Owner
- Name: SymbolicML
- Login: SymbolicML
- Kind: organization
- Repositories: 2
- Profile: https://github.com/SymbolicML
Citation (CITATION.cff)
cff-version: 1.2.0 message: "If you use this software, please cite it using as below." authors: - family-names: "Cranmer" given-names: "Miles" orcid: "https://orcid.org/0000-0002-6458-3423" title: "Interpretable Machine Learning for Science with PySR & SymbolicRegression.jl" version: 1.0.0 date-released: 2023-05-02 doi: 10.48550/arXiv.2305.01582 url: "https://github.com/MilesCranmer/pysr_paper"
GitHub Events
Total
- Create event: 48
- Commit comment event: 37
- Issues event: 3
- Release event: 19
- Watch event: 12
- Delete event: 15
- Issue comment event: 78
- Push event: 201
- Pull request review event: 7
- Pull request review comment event: 2
- Pull request event: 50
- Fork event: 3
Last Year
- Create event: 48
- Commit comment event: 37
- Issues event: 3
- Release event: 19
- Watch event: 12
- Delete event: 15
- Issue comment event: 78
- Push event: 201
- Pull request review event: 7
- Pull request review comment event: 2
- Pull request event: 50
- Fork event: 3
Issues and Pull Requests
Last synced: 6 months ago
All Time
- Total issues: 13
- Total pull requests: 62
- Average time to close issues: 5 days
- Average time to close pull requests: 8 days
- Total issue authors: 5
- Total pull request authors: 6
- Average comments per issue: 5.54
- Average comments per pull request: 2.08
- Merged pull requests: 43
- Bot issues: 0
- Bot pull requests: 5
Past Year
- Issues: 1
- Pull requests: 18
- Average time to close issues: 4 days
- Average time to close pull requests: 13 days
- Issue authors: 1
- Pull request authors: 3
- Average comments per issue: 5.0
- Average comments per pull request: 1.28
- Merged pull requests: 10
- Bot issues: 0
- Bot pull requests: 2
Top Authors
Issue Authors
- MilesCranmer (11)
- avik-pal (2)
- AlCap23 (1)
- github-actions[bot] (1)
- liuyxpp (1)
- nmheim (1)
- maxreiss123 (1)
- JuliaTagBot (1)
Pull Request Authors
- MilesCranmer (93)
- github-actions[bot] (7)
- sweep-ai[bot] (3)
- robdancer (2)
- nmheim (1)
- gca30 (1)
- timholy (1)
- sunxd3 (1)
- psaunderualberta (1)
- AlCap23 (1)
- charishma13 (1)
Top Labels
Issue Labels
Pull Request Labels
Packages
- Total packages: 1
-
Total downloads:
- julia 3,532 total
- Total dependent packages: 1
- Total dependent repositories: 0
- Total versions: 71
juliahub.com: DynamicExpressions
Ridiculously fast symbolic expressions
- Homepage: https://ai.damtp.cam.ac.uk/dynamicexpressions
- Documentation: https://docs.juliahub.com/General/DynamicExpressions/stable/
- License: Apache-2.0
-
Latest release: 2.3.0
published 8 months ago
Rankings
Dependencies
- actions/checkout v2 composite
- coverallsapp/github-action master composite
- julia-actions/cache v1 composite
- julia-actions/julia-buildpkg v1 composite
- julia-actions/setup-julia v1 composite
- actions/checkout v2 composite
- julia-actions/setup-julia latest composite
- JuliaRegistries/TagBot v1 composite
- actions/checkout v1 composite
- julia-actions/setup-julia latest composite
- actions/checkout v2 composite
- peter-evans/create-pull-request v3 composite
- actions/checkout v2 composite
- actions/upload-artifact v2 composite
- julia-actions/cache v1 composite
- julia-actions/setup-julia v1 composite
- peter-evans/create-or-update-comment v3 composite
- peter-evans/find-comment v2 composite