Skip to content

Conversation

@DhairyaLGandhi
Copy link
Contributor

@DhairyaLGandhi DhairyaLGandhi commented Nov 17, 2025

Adds an API that can define and apply optimizing rules on existing IR. Also allows for downstream packages to use as a hook to add additional rules as needed.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 17, 2025

Benchmark Results (Julia vlts)

Time benchmarks
master aedec3b... master / aedec3b...
arithmetic/addition 0.0775 ± 0.0008 ms 0.074 ± 0.00066 ms 1.05 ± 0.014
arithmetic/division 24.7 ± 0.34 μs 25.5 ± 0.44 μs 0.966 ± 0.021
arithmetic/multiplication 0.0596 ± 0.0016 ms 0.0579 ± 0.0023 ms 1.03 ± 0.05
overhead/acrule/a+2 2.37 ± 0.054 μs 2.69 ± 0.08 μs 0.879 ± 0.033
overhead/acrule/a+2+b 0.063 ± 0.018 μs 0.062 ± 0.002 μs 1.02 ± 0.29
overhead/acrule/a+b 3.99 ± 0.087 μs 4.65 ± 0.14 μs 0.858 ± 0.032
overhead/acrule/noop:Int 0.041 ± 0.002 μs 0.041 ± 0.001 μs 1 ± 0.055
overhead/acrule/noop:Sym 0.048 ± 0.002 μs 0.047 ± 0.001 μs 1.02 ± 0.048
overhead/get_degrees/large_poly 0.108 ± 0.006 s 0.0958 ± 0.0067 s 1.13 ± 0.1
overhead/rule/noop:Int 0.062 ± 0.003 μs 0.06 ± 0.002 μs 1.03 ± 0.061
overhead/rule/noop:Sym 0.059 ± 0.002 μs 0.059 ± 0.002 μs 1 ± 0.048
overhead/rule/noop:Term 0.059 ± 0.002 μs 0.059 ± 0.002 μs 1 ± 0.048
overhead/ruleset/noop:Int 19 ± 1 ns 19 ± 1 ns 1 ± 0.074
overhead/ruleset/noop:Sym 0.288 ± 0.039 μs 0.314 ± 0.007 μs 0.917 ± 0.13
overhead/ruleset/noop:Term 1.29 ± 0.022 μs 1.28 ± 0.022 μs 1 ± 0.024
overhead/simplify/noop:Int 0.037 ± 0.018 μs 19 ± 1 ns 1.95 ± 0.95
overhead/simplify/noop:Sym 25 ± 1 ns 26 ± 2 ns 0.962 ± 0.083
overhead/simplify/noop:Term 26.8 ± 0.68 μs 30.2 ± 0.88 μs 0.89 ± 0.034
overhead/simplify/randterm (+, *):serial 0.22 ± 0.00092 s 0.255 ± 0.007 s 0.863 ± 0.024
overhead/simplify/randterm (+, *):thread 0.297 ± 0.14 s 0.315 ± 0.14 s 0.944 ± 0.61
overhead/simplify/randterm (/, *):serial 0.0808 ± 0.002 ms 0.086 ± 0.0023 ms 0.94 ± 0.034
overhead/simplify/randterm (/, *):thread 0.0827 ± 0.0023 ms 0.0886 ± 0.0025 ms 0.933 ± 0.037
overhead/substitute/a 0.046 ± 0.00059 ms 0.0467 ± 0.00065 ms 0.984 ± 0.019
overhead/substitute/a,b 0.057 ± 0.00067 ms 0.0583 ± 0.00071 ms 0.977 ± 0.017
overhead/substitute/a,b,c 0.0517 ± 0.00059 ms 0.0518 ± 0.00063 ms 0.998 ± 0.017
polyform/easy_iszero 22.1 ± 0.27 μs 21.7 ± 0.31 μs 1.02 ± 0.019
polyform/isone 0.977 ± 0.023 ms 0.981 ± 0.022 ms 0.996 ± 0.032
polyform/isone:noop 0.166 ± 0.023 μs 0.14 ± 0.024 μs 1.19 ± 0.26
polyform/iszero 0.818 ± 0.018 ms 0.82 ± 0.018 ms 0.997 ± 0.031
polyform/iszero:noop 0.159 ± 0.023 μs 0.139 ± 0.004 μs 1.14 ± 0.17
polyform/simplify_fractions 1.06 ± 0.023 ms 1.08 ± 0.024 ms 0.979 ± 0.03
time_to_load 1.19 ± 0.038 s 1.19 ± 0.0061 s 0.995 ± 0.032
Memory benchmarks
master aedec3b... master / aedec3b...
arithmetic/addition 0.438 k allocs: 16 kB 0.438 k allocs: 16 kB 1
arithmetic/division 0.147 k allocs: 5.48 kB 0.198 k allocs: 6.89 kB 0.796
arithmetic/multiplication 0.358 k allocs: 11.7 kB 0.357 k allocs: 11.7 kB 1
overhead/acrule/a+2 0.034 k allocs: 1.25 kB 0.037 k allocs: 1.29 kB 0.97
overhead/acrule/a+2+b 0 allocs: 0 B 0 allocs: 0 B
overhead/acrule/a+b 0.047 k allocs: 1.8 kB 0.053 k allocs: 1.88 kB 0.958
overhead/acrule/noop:Int 0 allocs: 0 B 0 allocs: 0 B
overhead/acrule/noop:Sym 0 allocs: 0 B 0 allocs: 0 B
overhead/get_degrees/large_poly 0.712 M allocs: 20.6 MB 0.712 M allocs: 20.6 MB 1
overhead/rule/noop:Int 2 allocs: 0.0625 kB 2 allocs: 0.0625 kB 1
overhead/rule/noop:Sym 2 allocs: 0.0625 kB 2 allocs: 0.0625 kB 1
overhead/rule/noop:Term 2 allocs: 0.0625 kB 2 allocs: 0.0625 kB 1
overhead/ruleset/noop:Int 0 allocs: 0 B 0 allocs: 0 B
overhead/ruleset/noop:Sym 3 allocs: 0.109 kB 3 allocs: 0.109 kB 1
overhead/ruleset/noop:Term 12 allocs: 0.391 kB 12 allocs: 0.391 kB 1
overhead/simplify/noop:Int 0 allocs: 0 B 0 allocs: 0 B
overhead/simplify/noop:Sym 0 allocs: 0 B 0 allocs: 0 B
overhead/simplify/noop:Term 0.298 k allocs: 11.6 kB 0.388 k allocs: 14.4 kB 0.806
overhead/simplify/randterm (+, *):serial 2.31 M allocs: 0.0883 GB 2.95 M allocs: 0.106 GB 0.83
overhead/simplify/randterm (+, *):thread 2.34 M allocs: 0.246 GB 2.96 M allocs: 0.263 GB 0.935
overhead/simplify/randterm (/, *):serial 0.848 k allocs: 30.5 kB 0.869 k allocs: 30.8 kB 0.991
overhead/simplify/randterm (/, *):thread 0.873 k allocs: 31.2 kB 0.894 k allocs: 31.5 kB 0.991
overhead/substitute/a 0.272 k allocs: 9.83 kB 0.308 k allocs: 11 kB 0.897
overhead/substitute/a,b 0.34 k allocs: 12.2 kB 0.394 k allocs: 13.9 kB 0.879
overhead/substitute/a,b,c 0.301 k allocs: 10.4 kB 0.355 k allocs: 12.1 kB 0.86
polyform/easy_iszero 0.132 k allocs: 4.47 kB 0.146 k allocs: 4.92 kB 0.908
polyform/isone 8.65 k allocs: 0.572 MB 9.22 k allocs: 0.589 MB 0.972
polyform/isone:noop 2 allocs: 32 B 2 allocs: 32 B 1
polyform/iszero 7.14 k allocs: 0.472 MB 7.58 k allocs: 0.484 MB 0.974
polyform/iszero:noop 2 allocs: 32 B 2 allocs: 32 B 1
polyform/simplify_fractions 9.19 k allocs: 0.595 MB 10.3 k allocs: 0.627 MB 0.95
time_to_load 0.153 k allocs: 14.5 kB 0.153 k allocs: 14.5 kB 1

Copy link
Member

@AayushSabharwal AayushSabharwal left a comment

Choose a reason for hiding this comment

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

Would it also help if I changed CSE so that the i'th element can be referred to with something like Code.cse_var[i]?

OptimizationRule(name, detector, transformer, priority)
Defines an optimization rule with:
- `name`: A string identifier for the optimization.
Copy link
Member

Choose a reason for hiding this comment

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

If we're enforcing that the name is a string, should the field just be typed as ::String?

Copy link
Member

Choose a reason for hiding this comment

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

The same applies for priority. Why not just make it Int? Less prone to errors.

The detector function should implement the signature
```julia
detector(expr::Code.Let, state::Code.CSEState) -> Union{Nothing, Vector{<:AbstractMatched}}
Copy link
Member

Choose a reason for hiding this comment

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

Why does this need to return a Union? Can we just always return a Vector and check isempty? We should also document exactly what expr and state are, what modifications we can/cannot make to them, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nothing is easier to ID imo, and can be used to dispatch as a shortcut if needed.

priority::P
end

abstract type AbstractMatched end
Copy link
Member

Choose a reason for hiding this comment

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

Is there some sort of interface on AbstractMatched?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's more intended to be a part of the interface for OptimizationRule which returns matched objects that get passed to the transformation function.

Copy link
Member

Choose a reason for hiding this comment

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

Right, but if AbstractMatched has no contract it doesn't really have a reason to exist? Or will a contract be added in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The basic case is that the matched objects might contain wildly different fields/ data. It is specific to the optimization. What kind of contract did you have in mind? Its a container that passes information from the detection to the transformation.

src/code.jl Outdated

abstract type AbstractMatched end

function bank(dic, key, value)
Copy link
Member

Choose a reason for hiding this comment

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

All of these utility functions should be documented. It's not clear what the arguments are or what the function is intended to do.

Base.isempty(l::Code.Let) = isempty(l.pairs)

# Apply optimization rules during CSE
function apply_optimization_rules(expr, state::Code.CSEState, rules)
Copy link
Member

Choose a reason for hiding this comment

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

The argument is named rules (along with the function name) but the function body treats it as a single rule?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yes, I previously had it as a for loop over the rules sorted by priority. That way we could pass a number of rules transforming the code at every step.I had run into the case that that would require the detection functions to handle things like aliasing, which they should be aware of anyway but does add some complexity at the get go. If preferred we can add that aa a requirement and I'll work on checking what code we have that could potentially run afowl of these subtleties

@github-actions
Copy link
Contributor

Benchmark Results (Julia v1)

Time benchmarks
master aedec3b... master / aedec3b...
arithmetic/addition 0.0683 ± 0.0007 ms 0.0678 ± 0.00085 ms 1.01 ± 0.016
arithmetic/division 25.5 ± 0.56 μs 27.8 ± 0.77 μs 0.918 ± 0.033
arithmetic/multiplication 0.0513 ± 0.0018 ms 0.0525 ± 0.0019 ms 0.976 ± 0.05
overhead/acrule/a+2 2.19 ± 0.04 μs 2.8 ± 0.081 μs 0.785 ± 0.027
overhead/acrule/a+2+b 0.08 ± 0 μs 0.08 ± 0.01 μs 1 ± 0.12
overhead/acrule/a+b 3.85 ± 0.059 μs 5.05 ± 0.14 μs 0.762 ± 0.024
overhead/acrule/noop:Int 30 ± 0 ns 30 ± 0 ns 1 ± 0
overhead/acrule/noop:Sym 0.07 ± 0.01 μs 0.061 ± 0.01 μs 1.15 ± 0.25
overhead/get_degrees/large_poly 0.0942 ± 0.0076 s 0.0932 ± 0.0075 s 1.01 ± 0.12
overhead/rule/noop:Int 0.07 ± 0.01 μs 0.07 ± 0.01 μs 1 ± 0.2
overhead/rule/noop:Sym 0.07 ± 0.001 μs 0.07 ± 0.009 μs 1 ± 0.13
overhead/rule/noop:Term 0.07 ± 0 μs 0.07 ± 0.01 μs 1 ± 0.14
overhead/ruleset/noop:Int 30 ± 0 ns 30 ± 0 ns 1 ± 0
overhead/ruleset/noop:Sym 0.311 ± 0.01 μs 0.311 ± 0.001 μs 1 ± 0.032
overhead/ruleset/noop:Term 1.18 ± 0.02 μs 1.21 ± 0.04 μs 0.975 ± 0.036
overhead/simplify/noop:Int 30 ± 0 ns 30 ± 0 ns 1 ± 0
overhead/simplify/noop:Sym 0.07 ± 0 μs 0.07 ± 0.01 μs 1 ± 0.14
overhead/simplify/noop:Term 26.6 ± 0.53 μs 0.0322 ± 0.00073 ms 0.824 ± 0.025
overhead/simplify/randterm (+, *):serial 0.191 ± 0.026 s 0.327 ± 0.043 s 0.585 ± 0.11
overhead/simplify/randterm (+, *):thread 0.223 ± 0.021 s 0.34 ± 0.029 s 0.658 ± 0.082
overhead/simplify/randterm (/, *):serial 0.0851 ± 0.0074 ms 0.0919 ± 0.0086 ms 0.925 ± 0.12
overhead/simplify/randterm (/, *):thread 0.0951 ± 0.0098 ms 0.102 ± 0.012 ms 0.93 ± 0.15
overhead/substitute/a 0.0408 ± 0.00078 ms 0.0441 ± 0.00095 ms 0.924 ± 0.027
overhead/substitute/a,b 0.0512 ± 0.00096 ms 0.0557 ± 0.0011 ms 0.92 ± 0.025
overhead/substitute/a,b,c 0.0493 ± 0.0011 ms 0.0525 ± 0.00099 ms 0.939 ± 0.027
polyform/easy_iszero 18.8 ± 0.32 μs 20.5 ± 0.49 μs 0.921 ± 0.027
polyform/isone 0.92 ± 0.016 ms 0.969 ± 0.016 ms 0.949 ± 0.023
polyform/isone:noop 0.15 ± 0.01 μs 0.141 ± 0.01 μs 1.06 ± 0.1
polyform/iszero 0.799 ± 0.015 ms 0.842 ± 0.015 ms 0.948 ± 0.025
polyform/iszero:noop 0.15 ± 0.01 μs 0.15 ± 0.01 μs 1 ± 0.094
polyform/simplify_fractions 0.999 ± 0.023 ms 1.08 ± 0.017 ms 0.927 ± 0.026
time_to_load 1.33 ± 0.018 s 1.33 ± 0.017 s 1 ± 0.019
Memory benchmarks
master aedec3b... master / aedec3b...
arithmetic/addition 0.3 k allocs: 10.3 kB 0.3 k allocs: 10.3 kB 1
arithmetic/division 0.136 k allocs: 4.8 kB 0.189 k allocs: 6.43 kB 0.746
arithmetic/multiplication 0.254 k allocs: 6.5 kB 0.253 k allocs: 6.47 kB 1
overhead/acrule/a+2 0.033 k allocs: 1.11 kB 0.035 k allocs: 1.13 kB 0.979
overhead/acrule/a+2+b 0 allocs: 0 B 0 allocs: 0 B
overhead/acrule/a+b 0.044 k allocs: 1.52 kB 0.048 k allocs: 1.56 kB 0.97
overhead/acrule/noop:Int 0 allocs: 0 B 0 allocs: 0 B
overhead/acrule/noop:Sym 0 allocs: 0 B 0 allocs: 0 B
overhead/get_degrees/large_poly 0.601 M allocs: 18.4 MB 0.601 M allocs: 18.4 MB 1
overhead/rule/noop:Int 2 allocs: 0.0625 kB 2 allocs: 0.0625 kB 1
overhead/rule/noop:Sym 2 allocs: 0.0625 kB 2 allocs: 0.0625 kB 1
overhead/rule/noop:Term 2 allocs: 0.0625 kB 2 allocs: 0.0625 kB 1
overhead/ruleset/noop:Int 0 allocs: 0 B 0 allocs: 0 B
overhead/ruleset/noop:Sym 3 allocs: 0.109 kB 3 allocs: 0.109 kB 1
overhead/ruleset/noop:Term 12 allocs: 0.391 kB 12 allocs: 0.391 kB 1
overhead/simplify/noop:Int 0 allocs: 0 B 0 allocs: 0 B
overhead/simplify/noop:Sym 1 allocs: 0.0625 kB 1 allocs: 0.0625 kB 1
overhead/simplify/noop:Term 0.275 k allocs: 9.92 kB 0.375 k allocs: 13.8 kB 0.718
overhead/simplify/randterm (+, *):serial 2.09 M allocs: 0.0744 GB 4.29 M allocs: 0.144 GB 0.518
overhead/simplify/randterm (+, *):thread 2.28 M allocs: 0.237 GB 4.17 M allocs: 0.296 GB 0.799
overhead/simplify/randterm (/, *):serial 0.78 k allocs: 28.2 kB 0.794 k allocs: 28.4 kB 0.994
overhead/simplify/randterm (/, *):thread 0.935 k allocs: 0.033 MB 0.949 k allocs: 0.0332 MB 0.995
overhead/substitute/a 0.204 k allocs: 6.77 kB 0.248 k allocs: 8.45 kB 0.8
overhead/substitute/a,b 0.268 k allocs: 8.86 kB 0.334 k allocs: 11.4 kB 0.778
overhead/substitute/a,b,c 0.268 k allocs: 8.67 kB 0.334 k allocs: 11.2 kB 0.774
polyform/easy_iszero 0.09 k allocs: 2.86 kB 0.106 k allocs: 3.5 kB 0.817
polyform/isone 11.6 k allocs: 0.593 MB 12.2 k allocs: 0.615 MB 0.965
polyform/isone:noop 2 allocs: 32 B 2 allocs: 32 B 1
polyform/iszero 9.53 k allocs: 0.49 MB 10 k allocs: 0.506 MB 0.968
polyform/iszero:noop 2 allocs: 32 B 2 allocs: 32 B 1
polyform/simplify_fractions 12.1 k allocs: 0.611 MB 13.3 k allocs: 0.65 MB 0.94
time_to_load 0.145 k allocs: 11 kB 0.145 k allocs: 11 kB 1

@AayushSabharwal AayushSabharwal merged commit 844e350 into JuliaSymbolics:master Nov 24, 2025
15 of 18 checks passed
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.

2 participants