Skip to content

Add promote_symtype for ComposedFunction (fixes #650)#859

Merged
AayushSabharwal merged 2 commits intoJuliaSymbolics:masterfrom
ParthsarthiSingh-glang:master
Feb 27, 2026
Merged

Add promote_symtype for ComposedFunction (fixes #650)#859
AayushSabharwal merged 2 commits intoJuliaSymbolics:masterfrom
ParthsarthiSingh-glang:master

Conversation

@ParthsarthiSingh-glang
Copy link
Contributor

@ParthsarthiSingh-glang ParthsarthiSingh-glang commented Feb 13, 2026

Summary

Adds promote_symtype support for composed functions (e.g., sin ∘ sqrt).

Problem

When you compose functions with , SymbolicUtils doesn't know how to infer the result type, so it returns Any instead of the actual type.

Solution

Unwrap the composition and fold type information from inner to outer function.

Before:

promote_symtype(sin  sqrt, Real)  # Returns: Any

After:

promote_symtype(sin  sqrt, Real)  # Returns: Real 

Testing

Added tests for 2, 3, and 4-function compositions with different input types. All pass.

Closes #650

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

Benchmark Results (Julia vlts)

Time benchmarks
master 3dbf44d... master / 3dbf44d...
arithmetic/2-arg mul 13.3 ± 0.7 μs 13.1 ± 0.7 μs 1.02 ± 0.076
arithmetic/addition 0.0765 ± 0.0013 ms 0.0777 ± 0.0013 ms 0.984 ± 0.023
arithmetic/division 24.2 ± 0.66 μs 24.7 ± 0.92 μs 0.979 ± 0.045
arithmetic/multiplication 0.0556 ± 0.0016 ms 0.0597 ± 0.0019 ms 0.932 ± 0.04
overhead/acrule/a+2 2.44 ± 0.11 μs 2.36 ± 0.13 μs 1.03 ± 0.073
overhead/acrule/a+2+b 0.07 ± 0 μs 0.07 ± 0.01 μs 1 ± 0.14
overhead/acrule/a+b 4.24 ± 0.2 μs 4.13 ± 0.34 μs 1.03 ± 0.097
overhead/acrule/noop:Int 0.05 ± 0 μs 0.05 ± 0.01 μs 1 ± 0.2
overhead/acrule/noop:Sym 0.06 ± 0.01 μs 0.06 ± 0.01 μs 1 ± 0.24
overhead/get_degrees/large_poly 0.09 ± 0.01 μs 0.11 ± 0.01 μs 0.818 ± 0.12
overhead/rule/noop:Int 0.06 ± 0.01 μs 0.06 ± 0.01 μs 1 ± 0.24
overhead/rule/noop:Sym 0.06 ± 0.01 μs 0.06 ± 0.01 μs 1 ± 0.24
overhead/rule/noop:Term 0.06 ± 0.01 μs 0.06 ± 0.01 μs 1 ± 0.24
overhead/ruleset/noop:Int 30 ± 0 ns 30 ± 0 ns 1 ± 0
overhead/ruleset/noop:Sym 0.29 ± 0.01 μs 0.28 ± 0.001 μs 1.04 ± 0.036
overhead/ruleset/noop:Term 1.16 ± 0.04 μs 1.18 ± 0.06 μs 0.982 ± 0.06
overhead/simplify/noop:Int 30 ± 0 ns 30 ± 0 ns 1 ± 0
overhead/simplify/noop:Sym 0.04 ± 0.01 μs 0.04 ± 0.01 μs 1 ± 0.35
overhead/simplify/noop:Term 28.2 ± 1.3 μs 27.7 ± 0.87 μs 1.02 ± 0.056
overhead/simplify/randterm (+, *):serial 0.243 ± 0.0016 s 0.241 ± 0.0014 s 1.01 ± 0.0087
overhead/simplify/randterm (+, *):thread 0.287 ± 0.029 s 0.267 ± 0.017 s 1.07 ± 0.13
overhead/simplify/randterm (/, *):serial 0.079 ± 0.0026 ms 0.0816 ± 0.0018 ms 0.968 ± 0.038
overhead/simplify/randterm (/, *):thread 0.0819 ± 0.0026 ms 0.085 ± 0.0018 ms 0.964 ± 0.037
overhead/substitute/a 0.0388 ± 0.00081 ms 0.039 ± 0.00078 ms 0.997 ± 0.029
overhead/substitute/a,b 0.0466 ± 0.00094 ms 0.0467 ± 0.00086 ms 0.997 ± 0.027
overhead/substitute/a,b,c 0.0415 ± 0.00082 ms 0.0423 ± 0.00079 ms 0.981 ± 0.027
polyform/easy_iszero 22.8 ± 0.51 μs 22.7 ± 0.54 μs 1 ± 0.033
polyform/isone 1.04 ± 0.043 ms 1.05 ± 0.039 ms 0.984 ± 0.055
polyform/isone:noop 0.09 ± 0.01 μs 0.101 ± 0.01 μs 0.891 ± 0.13
polyform/iszero 0.874 ± 0.038 ms 0.881 ± 0.024 ms 0.992 ± 0.051
polyform/iszero:noop 0.1 ± 0.03 μs 0.11 ± 0.009 μs 0.909 ± 0.28
polyform/simplify_fractions 1.11 ± 0.04 ms 1.12 ± 0.029 ms 0.993 ± 0.044
printing/large_poly 0.22 ± 0.001 s 0.227 ± 0.0041 s 0.969 ± 0.018
time_to_load 1.29 ± 0.0039 s 1.31 ± 0.0094 s 0.988 ± 0.0077
Memory benchmarks
master 3dbf44d... master / 3dbf44d...
arithmetic/2-arg mul 0.078 k allocs: 2.72 kB 0.078 k allocs: 2.72 kB 1
arithmetic/addition 0.438 k allocs: 16 kB 0.438 k allocs: 16 kB 1
arithmetic/division 0.142 k allocs: 5.47 kB 0.142 k allocs: 5.47 kB 1
arithmetic/multiplication 0.356 k allocs: 11.7 kB 0.356 k allocs: 11.7 kB 1
overhead/acrule/a+2 0.034 k allocs: 1.25 kB 0.034 k allocs: 1.25 kB 1
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.047 k allocs: 1.8 kB 1
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 2 allocs: 32 B 2 allocs: 32 B 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.298 k allocs: 11.6 kB 1
overhead/simplify/randterm (+, *):serial 2.3 M allocs: 0.0882 GB 2.3 M allocs: 0.0882 GB 0.999
overhead/simplify/randterm (+, *):thread 2.34 M allocs: 0.246 GB 2.35 M allocs: 0.246 GB 1
overhead/simplify/randterm (/, *):serial 0.844 k allocs: 30.4 kB 0.844 k allocs: 30.4 kB 1
overhead/simplify/randterm (/, *):thread 0.879 k allocs: 31.5 kB 0.879 k allocs: 31.5 kB 1
overhead/substitute/a 0.22 k allocs: 8.42 kB 0.22 k allocs: 8.42 kB 1
overhead/substitute/a,b 0.267 k allocs: 10.1 kB 0.267 k allocs: 10.1 kB 1
overhead/substitute/a,b,c 0.238 k allocs: 8.62 kB 0.238 k allocs: 8.62 kB 1
polyform/easy_iszero 0.133 k allocs: 4.58 kB 0.133 k allocs: 4.58 kB 1
polyform/isone 8.34 k allocs: 0.567 MB 8.34 k allocs: 0.567 MB 1
polyform/isone:noop 1 allocs: 16 B 1 allocs: 16 B 1
polyform/iszero 6.87 k allocs: 0.467 MB 6.87 k allocs: 0.467 MB 1
polyform/iszero:noop 1 allocs: 16 B 1 allocs: 16 B 1
polyform/simplify_fractions 8.82 k allocs: 0.59 MB 8.82 k allocs: 0.59 MB 1
printing/large_poly 1.86 M allocs: 0.082 GB 1.86 M allocs: 0.082 GB 1
time_to_load 0.153 k allocs: 14.5 kB 0.153 k allocs: 14.5 kB 1

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

Benchmark Results (Julia v1)

Time benchmarks
master 3dbf44d... master / 3dbf44d...
arithmetic/2-arg mul 10.7 ± 0.28 μs 10.6 ± 0.27 μs 1 ± 0.037
arithmetic/addition 0.0698 ± 0.00086 ms 0.0675 ± 0.00087 ms 1.03 ± 0.018
arithmetic/division 24.6 ± 0.53 μs 24.8 ± 0.64 μs 0.991 ± 0.033
arithmetic/multiplication 0.0512 ± 0.0017 ms 0.0532 ± 0.0016 ms 0.962 ± 0.043
overhead/acrule/a+2 2.23 ± 0.05 μs 2.11 ± 0.04 μs 1.05 ± 0.031
overhead/acrule/a+2+b 0.08 ± 0.01 μs 0.08 ± 0.01 μs 1 ± 0.18
overhead/acrule/a+b 3.84 ± 0.089 μs 3.68 ± 0.07 μs 1.04 ± 0.031
overhead/acrule/noop:Int 30 ± 0 ns 30 ± 0 ns 1 ± 0
overhead/acrule/noop:Sym 0.07 ± 0 μs 0.07 ± 0.01 μs 1 ± 0.14
overhead/get_degrees/large_poly 0.09 ± 0.01 μs 0.081 ± 0.01 μs 1.11 ± 0.18
overhead/rule/noop:Int 0.07 ± 0.01 μs 0.07 ± 0.01 μs 1 ± 0.2
overhead/rule/noop:Sym 0.07 ± 0 μs 0.07 ± 0.001 μs 1 ± 0.014
overhead/rule/noop:Term 0.07 ± 0 μs 0.07 ± 0.001 μs 1 ± 0.014
overhead/ruleset/noop:Int 30 ± 0 ns 30 ± 0 ns 1 ± 0
overhead/ruleset/noop:Sym 0.331 ± 0.031 μs 0.311 ± 0.02 μs 1.06 ± 0.12
overhead/ruleset/noop:Term 1.21 ± 0.03 μs 1.18 ± 0.03 μs 1.03 ± 0.036
overhead/simplify/noop:Int 30 ± 0 ns 30 ± 0 ns 1 ± 0
overhead/simplify/noop:Sym 30 ± 10 ns 30 ± 10 ns 1 ± 0.47
overhead/simplify/noop:Term 26.7 ± 0.52 μs 26.5 ± 0.6 μs 1.01 ± 0.03
overhead/simplify/randterm (+, *):serial 0.209 ± 0.033 s 0.185 ± 0.026 s 1.13 ± 0.24
overhead/simplify/randterm (+, *):thread 0.249 ± 0.052 s 0.247 ± 0.088 s 1.01 ± 0.42
overhead/simplify/randterm (/, *):serial 0.0861 ± 0.0044 ms 0.0876 ± 0.0047 ms 0.983 ± 0.073
overhead/simplify/randterm (/, *):thread 0.0963 ± 0.0093 ms 0.0977 ± 0.0099 ms 0.986 ± 0.14
overhead/substitute/a 0.0335 ± 0.00079 ms 0.0335 ± 0.00067 ms 1 ± 0.031
overhead/substitute/a,b 0.0411 ± 0.00094 ms 0.0419 ± 0.001 ms 0.982 ± 0.033
overhead/substitute/a,b,c 0.0399 ± 0.00086 ms 0.0397 ± 0.0009 ms 1.01 ± 0.032
polyform/easy_iszero 18.8 ± 0.43 μs 18.6 ± 0.38 μs 1.01 ± 0.031
polyform/isone 0.906 ± 0.024 ms 0.908 ± 0.021 ms 0.997 ± 0.035
polyform/isone:noop 0.081 ± 0.01 μs 0.08 ± 0.001 μs 1.01 ± 0.13
polyform/iszero 0.782 ± 0.019 ms 0.789 ± 0.019 ms 0.991 ± 0.033
polyform/iszero:noop 0.09 ± 0.01 μs 0.08 ± 0.01 μs 1.12 ± 0.19
polyform/simplify_fractions 0.972 ± 0.034 ms 0.981 ± 0.019 ms 0.991 ± 0.04
printing/large_poly 0.188 ± 0.014 s 0.189 ± 0.011 s 0.995 ± 0.094
time_to_load 1.41 ± 0.0047 s 1.43 ± 0.015 s 0.988 ± 0.011
Memory benchmarks
master 3dbf44d... master / 3dbf44d...
arithmetic/2-arg mul 0.056 k allocs: 1.78 kB 0.055 k allocs: 1.77 kB 1.01
arithmetic/addition 0.3 k allocs: 10.3 kB 0.3 k allocs: 10.3 kB 1
arithmetic/division 0.132 k allocs: 4.77 kB 0.131 k allocs: 4.75 kB 1
arithmetic/multiplication 0.252 k allocs: 6.5 kB 0.252 k allocs: 6.5 kB 1
overhead/acrule/a+2 0.034 k allocs: 1.12 kB 0.033 k allocs: 1.11 kB 1.01
overhead/acrule/a+2+b 0 allocs: 0 B 0 allocs: 0 B
overhead/acrule/a+b 0.046 k allocs: 1.55 kB 0.044 k allocs: 1.52 kB 1.02
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 2 allocs: 32 B 2 allocs: 32 B 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.284 k allocs: 10 kB 0.276 k allocs: 9.89 kB 1.01
overhead/simplify/randterm (+, *):serial 2.16 M allocs: 0.0754 GB 2.09 M allocs: 0.0744 GB 1.01
overhead/simplify/randterm (+, *):thread 2.32 M allocs: 0.237 GB 2.25 M allocs: 0.236 GB 1
overhead/simplify/randterm (/, *):serial 0.783 k allocs: 28.2 kB 0.776 k allocs: 28.1 kB 1
overhead/simplify/randterm (/, *):thread 0.918 k allocs: 0.0325 MB 0.911 k allocs: 0.0324 MB 1
overhead/substitute/a 0.172 k allocs: 6.05 kB 0.168 k allocs: 5.98 kB 1.01
overhead/substitute/a,b 0.223 k allocs: 7.69 kB 0.217 k allocs: 7.59 kB 1.01
overhead/substitute/a,b,c 0.229 k allocs: 7.81 kB 0.223 k allocs: 7.72 kB 1.01
polyform/easy_iszero 0.092 k allocs: 2.94 kB 0.091 k allocs: 2.92 kB 1.01
polyform/isone 10.9 k allocs: 0.579 MB 10.9 k allocs: 0.578 MB 1
polyform/isone:noop 1 allocs: 16 B 1 allocs: 16 B 1
polyform/iszero 8.97 k allocs: 0.48 MB 8.95 k allocs: 0.48 MB 1
polyform/iszero:noop 1 allocs: 16 B 1 allocs: 16 B 1
polyform/simplify_fractions 11.4 k allocs: 0.596 MB 11.4 k allocs: 0.595 MB 1
printing/large_poly 2.15 M allocs: 0.079 GB 2.15 M allocs: 0.079 GB 1
time_to_load 0.145 k allocs: 11 kB 0.145 k allocs: 11 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.

Thanks for the contribution! Your tests look pretty good. However, some changes are necessary here. I've outlined them below. Once they're addressed, please feel free to ping me again for a review.

src/methods.jl Outdated
"""
function promote_symtype(f::Base.ComposedFunction, arg_symtypes...)
# Unwrap the composition into a vector [innermost, ..., outermost]
funcs = Base.unwrap_composed(f)
Copy link
Member

Choose a reason for hiding this comment

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

Base.unwrap_composed is internal. This approach also makes every subsequent promote_symtype in this function a dynamic dispatch, since Julia cannot infer the type of each element in funcs. ComposedFunction documents its fields: the wrapped functions can be obtained as .inner and .outer. A better approach would be to call promote_symtype on .inner with arg_symtypes, and pass the result of that to promote_symtype on .outer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

src/methods.jl Outdated
the argument to the next function. Multi-argument returns (tuples) are not
currently supported but could be added if needed.
"""
function promote_symtype(f::Base.ComposedFunction, arg_symtypes...)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
function promote_symtype(f::Base.ComposedFunction, arg_symtypes...)
function promote_symtype(f::Base.ComposedFunction, arg_symtypes::TypeT...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@ParthsarthiSingh-glang
Copy link
Contributor Author

@AayushSabharwal

Changes implemented as suggested . Replaced Base.unwrap_composed with the recursive .inner/.outer approach.
Please review it once .
Thanks

@AayushSabharwal AayushSabharwal merged commit 540c557 into JuliaSymbolics:master Feb 27, 2026
16 of 21 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.

Definition of promote_symtype for ComposedFunctions

2 participants