Skip to content

feat: guard nonfinite inputs in LV @turbo compound unary eval#179

Open
MilesCranmerBot wants to merge 6 commits into
SymbolicML:masterfrom
MilesCranmerBot:bot/finite-safe-trait
Open

feat: guard nonfinite inputs in LV @turbo compound unary eval#179
MilesCranmerBot wants to merge 6 commits into
SymbolicML:masterfrom
MilesCranmerBot:bot/finite-safe-trait

Conversation

@MilesCranmerBot

@MilesCranmerBot MilesCranmerBot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

What

Guard nonfinite inputs in the 4 compound unary @turbo loops in the LoopVectorization extension that were missing ifelse(isfinite, ...) guards.

How

  • turbo_can_eval_nonfinite(op) returns true for operators that are safe to call on Inf/NaN (e.g. exp, abs, +, identity).
  • For safe ops: unguarded @turbo (zero overhead).
  • For unsafe ops (sin, log, sqrt, etc.): @turbo with ifelse(isfinite(x_l), op(x_l), T(NaN)).

Files

  • src/ValueInterface.jl: turbo_can_eval_nonfinite dispatch table (+9 lines)
  • ext/DynamicExpressionsLoopVectorizationExt.jl: guard 4 @turbo loops (+41/-16)
  • test/test_nonfinite_operator_guard.jl: tests (+39 lines)

Total: +90/-16 across 4 files (including test registration).

CI notes

Two failures appear pre-existing:

  • Julia 1 - ubuntu: map test in test_base.jl:107 (unrelated to this diff)
  • downgrade-compat (1): SymbolicUtils precompilation failure (dependency issue)

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results (Julia v1)

Time benchmarks
master a983cc6... master / a983cc6...
eval/ComplexF32/evaluation 7.56 ± 0.53 ms 7.49 ± 0.54 ms 1.01 ± 0.1
eval/ComplexF64/evaluation 10.8 ± 0.93 ms 10.9 ± 0.89 ms 0.995 ± 0.12
eval/Float32/derivative 10.8 ± 1.6 ms 11.1 ± 1.5 ms 0.975 ± 0.2
eval/Float32/derivative_turbo 10.9 ± 2.3 ms 11 ± 1.7 ms 0.997 ± 0.26
eval/Float32/evaluation 2.6 ± 0.27 ms 2.61 ± 0.28 ms 0.997 ± 0.15
eval/Float32/evaluation_bumper 0.641 ± 0.017 ms 0.639 ± 0.017 ms 1 ± 0.038
eval/Float32/evaluation_turbo 0.577 ± 0.036 ms 0.581 ± 0.036 ms 0.993 ± 0.086
eval/Float32/evaluation_turbo_bumper 0.636 ± 0.017 ms 0.643 ± 0.017 ms 0.988 ± 0.037
eval/Float64/derivative 14.6 ± 4 ms 14.1 ± 3.4 ms 1.03 ± 0.38
eval/Float64/derivative_turbo 14.9 ± 4.1 ms 14.5 ± 3.7 ms 1.03 ± 0.39
eval/Float64/evaluation 3.25 ± 0.37 ms 3.14 ± 0.32 ms 1.04 ± 0.16
eval/Float64/evaluation_bumper 1.34 ± 0.047 ms 1.35 ± 0.043 ms 0.998 ± 0.047
eval/Float64/evaluation_turbo 1.11 ± 0.069 ms 1.1 ± 0.064 ms 1.01 ± 0.086
eval/Float64/evaluation_turbo_bumper 1.34 ± 0.049 ms 1.34 ± 0.044 ms 1 ± 0.049
utils/combine_operators/break_sharing 30.8 ± 4.3 μs 0.0377 ± 0.0099 ms 0.817 ± 0.24
utils/convert/break_sharing 28.7 ± 5.8 μs 25.9 ± 6.5 μs 1.11 ± 0.36
utils/convert/preserve_sharing 0.103 ± 0.0056 ms 0.102 ± 0.0062 ms 1 ± 0.082
utils/copy/break_sharing 29.6 ± 5 μs 30.1 ± 7 μs 0.984 ± 0.28
utils/copy/preserve_sharing 0.103 ± 0.0065 ms 0.102 ± 0.0067 ms 1.01 ± 0.092
utils/count_constant_nodes/break_sharing 10.3 ± 1.3 μs 9.33 ± 1.8 μs 1.11 ± 0.26
utils/count_constant_nodes/preserve_sharing 0.0882 ± 0.0056 ms 0.0881 ± 0.0058 ms 1 ± 0.091
utils/count_depth/break_sharing 10.8 ± 0.85 μs 10 ± 0.83 μs 1.08 ± 0.12
utils/count_nodes/break_sharing 10.2 ± 0.82 μs 9.49 ± 0.98 μs 1.07 ± 0.14
utils/count_nodes/preserve_sharing 0.0894 ± 0.0055 ms 0.0894 ± 0.0064 ms 1 ± 0.095
utils/get_set_constants!/break_sharing 29.2 ± 7.1 μs 28.3 ± 7.9 μs 1.03 ± 0.38
utils/get_set_constants!/preserve_sharing 0.184 ± 0.011 ms 0.184 ± 0.011 ms 1 ± 0.084
utils/get_set_constants_parametric 0.0386 ± 0.0052 ms 0.0397 ± 0.0061 ms 0.972 ± 0.2
utils/has_constants/break_sharing 5.21 ± 0.62 μs 5.36 ± 0.91 μs 0.972 ± 0.2
utils/has_operators/break_sharing 2.39 ± 0.26 μs 2.71 ± 0.27 μs 0.884 ± 0.13
utils/hash/break_sharing 21.3 ± 1 μs 21.2 ± 1.9 μs 1.01 ± 0.1
utils/hash/preserve_sharing 0.106 ± 0.006 ms 0.104 ± 0.0064 ms 1.02 ± 0.085
utils/index_constant_nodes/break_sharing 29.3 ± 2.9 μs 27.1 ± 5 μs 1.08 ± 0.23
utils/index_constant_nodes/preserve_sharing 0.105 ± 0.0063 ms 0.103 ± 0.0072 ms 1.02 ± 0.094
utils/is_constant/break_sharing 5.52 ± 0.73 μs 4.88 ± 0.62 μs 1.13 ± 0.21
utils/simplify_tree/break_sharing 28.8 ± 3.4 μs 27.5 ± 2.9 μs 1.05 ± 0.16
utils/simplify_tree/preserve_sharing 0.116 ± 0.006 ms 0.114 ± 0.0059 ms 1.02 ± 0.074
utils/string_tree/break_sharing 0.468 ± 0.016 ms 0.471 ± 0.017 ms 0.994 ± 0.049
utils/string_tree/preserve_sharing 0.572 ± 0.017 ms 0.567 ± 0.02 ms 1.01 ± 0.046
time_to_load 0.186 ± 0.0028 s 0.168 ± 0.0021 s 1.11 ± 0.022
Memory benchmarks
master a983cc6... master / a983cc6...
eval/ComplexF32/evaluation 0.978 k allocs: 2.5 MB 0.978 k allocs: 2.5 MB 1
eval/ComplexF64/evaluation 1.01 k allocs: 5.15 MB 0.999 k allocs: 5.09 MB 1.01
eval/Float32/derivative 4.62 k allocs: 17.4 MB 4.63 k allocs: 17.4 MB 0.996
eval/Float32/derivative_turbo 4.7 k allocs: 17.7 MB 4.66 k allocs: 17.5 MB 1.01
eval/Float32/evaluation 0.987 k allocs: 1.29 MB 0.975 k allocs: 1.27 MB 1.01
eval/Float32/evaluation_bumper 0.303 k allocs: 0.393 MB 0.303 k allocs: 0.393 MB 1
eval/Float32/evaluation_turbo 0.975 k allocs: 1.27 MB 0.966 k allocs: 1.26 MB 1.01
eval/Float32/evaluation_turbo_bumper 0.303 k allocs: 0.393 MB 0.303 k allocs: 0.393 MB 1
eval/Float64/derivative 4.73 k allocs: 0.0346 GB 4.81 k allocs: 0.0352 GB 0.982
eval/Float64/derivative_turbo 4.76 k allocs: 0.0348 GB 4.83 k allocs: 0.0353 GB 0.986
eval/Float64/evaluation 0.975 k allocs: 2.5 MB 1.01 k allocs: 2.57 MB 0.97
eval/Float64/evaluation_bumper 0.303 k allocs: 0.771 MB 0.303 k allocs: 0.771 MB 1
eval/Float64/evaluation_turbo 0.996 k allocs: 2.55 MB 0.999 k allocs: 2.56 MB 0.997
eval/Float64/evaluation_turbo_bumper 0.303 k allocs: 0.771 MB 0.303 k allocs: 0.771 MB 1
utils/combine_operators/break_sharing 4 allocs: 0.953 kB 4 allocs: 0.953 kB 1
utils/convert/break_sharing 2 k allocs: 0.123 MB 2 k allocs: 0.123 MB 1
utils/convert/preserve_sharing 2.4 k allocs: 0.192 MB 2.4 k allocs: 0.192 MB 1
utils/copy/break_sharing 2 k allocs: 0.123 MB 2 k allocs: 0.123 MB 1
utils/copy/preserve_sharing 2.4 k allocs: 0.192 MB 2.4 k allocs: 0.192 MB 1
utils/count_constant_nodes/break_sharing 4 allocs: 0.953 kB 4 allocs: 0.953 kB 1
utils/count_constant_nodes/preserve_sharing 0.404 k allocs: 0.0696 MB 0.404 k allocs: 0.0696 MB 1
utils/count_depth/break_sharing 4 allocs: 0.953 kB 4 allocs: 0.953 kB 1
utils/count_nodes/break_sharing 4 allocs: 0.953 kB 4 allocs: 0.953 kB 1
utils/count_nodes/preserve_sharing 0.404 k allocs: 0.0696 MB 0.404 k allocs: 0.0696 MB 1
utils/get_set_constants!/break_sharing 0.898 k allocs: 25.2 kB 0.898 k allocs: 25.2 kB 1
utils/get_set_constants!/preserve_sharing 1.7 k allocs: 0.138 MB 1.7 k allocs: 0.138 MB 1
utils/get_set_constants_parametric 1.42 k allocs: 0.0663 MB 1.42 k allocs: 0.0663 MB 1
utils/has_constants/break_sharing 4 allocs: 0.203 kB 4 allocs: 0.203 kB 1
utils/has_operators/break_sharing 4 allocs: 0.203 kB 4 allocs: 0.203 kB 1
utils/hash/break_sharing 0.104 k allocs: 2.52 kB 0.104 k allocs: 2.52 kB 1
utils/hash/preserve_sharing 0.504 k allocs: 0.0711 MB 0.504 k allocs: 0.0711 MB 1
utils/index_constant_nodes/break_sharing 2.1 k allocs: 0.094 MB 2.1 k allocs: 0.094 MB 1
utils/index_constant_nodes/preserve_sharing 2.5 k allocs: 0.163 MB 2.5 k allocs: 0.163 MB 1
utils/is_constant/break_sharing 4 allocs: 0.203 kB 4 allocs: 0.203 kB 1
utils/simplify_tree/break_sharing 0.104 k allocs: 2.52 kB 0.104 k allocs: 2.52 kB 1
utils/simplify_tree/preserve_sharing 0.504 k allocs: 0.0711 MB 0.504 k allocs: 0.0711 MB 1
utils/string_tree/break_sharing 11.9 k allocs: 0.999 MB 11.9 k allocs: 0.999 MB 1
utils/string_tree/preserve_sharing 12.3 k allocs: 1.07 MB 12.3 k allocs: 1.07 MB 1
time_to_load 0.145 k allocs: 11 kB 0.145 k allocs: 11 kB 1

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.00%. Comparing base (efbc2aa) to head (a983cc6).

Files with missing lines Patch % Lines
ext/DynamicExpressionsLoopVectorizationExt.jl 0.00% 36 Missing ⚠️
src/ValueInterface.jl 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #179      +/-   ##
==========================================
- Coverage   59.43%   59.00%   -0.44%     
==========================================
  Files          30       30              
  Lines        2682     2700      +18     
==========================================
- Hits         1594     1593       -1     
- Misses       1088     1107      +19     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MilesCranmerBot MilesCranmerBot force-pushed the bot/finite-safe-trait branch 3 times, most recently from 9ee6040 to 9d55269 Compare June 26, 2026 20:09
The @turbo compound unary evaluators called the outer operator even when the inner result was nonfinite. Guard operators that cannot be evaluated on nonfinite inputs; keep the unguarded path for operators that can.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
@MilesCranmerBot MilesCranmerBot force-pushed the bot/finite-safe-trait branch from 9d55269 to b2e05d7 Compare June 27, 2026 19:07
@MilesCranmer

Copy link
Copy Markdown
Member

@MilesCranmerBot could we please rename this to turbo_can_eval_nonfinite? Since this is specific to LV stuff.

Also please update the PR body and title

Per review feedback, the name should be specific to LoopVectorization
since it only guards @turbo loops.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
@MilesCranmerBot MilesCranmerBot changed the title feat: add FiniteSafe/FiniteUnsafe trait for Inf-safe operator dispatch in @turbo loops feat: guard nonfinite inputs in LV @turbo compound unary eval Jun 28, 2026
@MilesCranmer

Copy link
Copy Markdown
Member

@MilesCranmerBot the downgrade-compat CI is failing. Could you please fix that?

If unrelated to this PR, then make a new PR that fixes it.

@MilesCranmer

Copy link
Copy Markdown
Member

@MilesCranmerBot your code coverage is still dropping relative to master. Please fix.

- Add tests covering both branches (turbo_can_eval_nonfinite true/false)
  for all compound binary+unary evaluation paths in the LV extension
- Bump LoopVectorization compat floor from 0.12 to 0.12.170 to fix
  downgrade-compat CI failure (old LV versions crash on Julia 1.12)
- Remove extra blank line flagged by JuliaFormatter
- Bump SymbolicUtils compat from 4 to 4.27 (old versions crash on
  Julia 1.12 with SCALARS undefvar error)
LV 0.12.170 still has GlobalRef bug on Julia 1.12 CI runners.
Bump to latest 0.12.174. SymbolicUtils 4.35 matches arenanode-prototype
which was the last branch with passing downgrade-compat on Julia 1.10.
The julia-downgrade-compat action merges main+test projects. Without
matching compat entries in test/Project.toml, the test env tries to
resolve different versions than the main project, causing
'can not merge projects' on Julia 1.10.
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