Skip to content

Try to improve type stability with Householder#184

Merged
lkdvos merged 13 commits intomainfrom
ld-typestability
Mar 13, 2026
Merged

Try to improve type stability with Householder#184
lkdvos merged 13 commits intomainfrom
ld-typestability

Conversation

@lkdvos
Copy link
Member

@lkdvos lkdvos commented Mar 9, 2026

Here I standardized the Householder keyword arguments in an attempt to improve the type stability (again).

Apparently this just isn't enough, so I gave up again and just put the @assume_effects into place.
In general, I think this really is what we want, and the main point is that this should really only go wrong if default_algorithm isn't foldable, which is probably a fair thing to ask for.
I am also quite unhappy about this solution, and am very open to alternative suggestions, but I spent way too long on this already and am running out of ideas.

To give some insight into what I found:
The main thing seems to be that there is a maximal depth that the compiler will @inline or const-prop. For example, with some tinkering I actually got it to the point where select_algorithm(right_orth!, A, alg) is type stable, but select_algorithm(right_orth, A, alg) then wasn't, even though it is marked with inline annotations.
From this I concluded there is a maximal depth of logic we can achieve, and since the "orthnull" already is using up quite a bit of that I can't seem to get past it without a major rewrite

@codecov
Copy link

codecov bot commented Mar 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
ext/MatrixAlgebraKitGenericLinearAlgebraExt.jl 85.91% <100.00%> (ø)
src/algorithms.jl 88.73% <100.00%> (-1.27%) ⬇️
src/implementations/lq.jl 95.06% <100.00%> (+0.87%) ⬆️
src/implementations/qr.jl 95.08% <100.00%> (+0.94%) ⬆️
src/interface/decompositions.jl 70.37% <100.00%> (+6.37%) ⬆️
src/interface/lq.jl 62.50% <100.00%> (ø)
src/interface/orthnull.jl 70.58% <100.00%> (ø)
src/interface/qr.jl 62.50% <100.00%> (ø)

... and 3 files with indirect coverage changes

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

@lkdvos lkdvos force-pushed the ld-typestability branch 3 times, most recently from cb03fcf to 4535feb Compare March 9, 2026 20:22
@lkdvos lkdvos force-pushed the ld-typestability branch from 4535feb to 6d6e3f1 Compare March 10, 2026 12:38
@lkdvos lkdvos requested review from Jutho and kshyatt March 10, 2026 14:37

# Utility generated function to canonicalize keys in type-stable way
@generated _sortkeys(nt::NamedTuple{K}) where {K} =
:(NamedTuple{$(Tuple(sort!(collect(K))))}(nt))
Copy link
Member

Choose a reason for hiding this comment

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

Any reason for not doing

Suggested change
:(NamedTuple{$(Tuple(sort!(collect(K))))}(nt))
:(NamedTuple{$(sort(K))}(nt))

Copy link
Member

Choose a reason for hiding this comment

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

Also, don't know if this really requires a generated function:

julia> function _canonicalize_nt(x::NamedTuple{N}) where {N}
           return NamedTuple{sort(N)}(x)
       end
_canonicalize_nt (generic function with 1 method)

julia> _canonicalize_nt((; y=3, x=5))
(x = 5, y = 3)

julia> @code_warntype _canonicalize_nt((; y=3, x=5))
MethodInstance for _canonicalize_nt(::@NamedTuple{y::Int64, x::Int64})
  from _canonicalize_nt(x::NamedTuple{N}) where N @ Main REPL[10]:1
Static Parameters
  N = (:y, :x)
Arguments
  #self#::Core.Const(Main._canonicalize_nt)
  x::@NamedTuple{y::Int64, x::Int64}
Body::@NamedTuple{x::Int64, y::Int64}
1 ─ %1 = Main.NamedTuple::Core.Const(NamedTuple)
│   %2 = Main.sort::Core.Const(sort)
│   %3 = $(Expr(:static_parameter, 1))::Core.Const((:y, :x))
│   %4 = (%2)(%3)::Core.Const((:x, :y))
│   %5 = Core.apply_type(%1, %4)::Core.Const(NamedTuple{(:x, :y)})
│   %6 = (%5)(x)::@NamedTuple{x::Int64, y::Int64}
└──      return %6

Copy link
Member Author

@lkdvos lkdvos Mar 12, 2026

Choose a reason for hiding this comment

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

I feel like I tried that but that didn't work, let me double check

Copy link
Member Author

Choose a reason for hiding this comment

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

Can confirm this doesn't work on Julia 1.10 😉

@Jutho
Copy link
Member

Jutho commented Mar 12, 2026

I would like to see a final run with the non-generated function suggestion to canonicalize the NamedTuple field of Algorithm{Name}. Also, the qr and lq tests use the deprecated explicit LAPACK_HouseholderQR/LQ algorithms. I think these should probably have been fixed in the original PR, so maybe this can be included here?

@lkdvos
Copy link
Member Author

lkdvos commented Mar 13, 2026

@Jutho I think I have now more or less exhausted the ways of writing a generated function without using a generated function. The verdict is that on LTS sort(::Tuple{Vararg{Symbol}}) does not work, requiring Tuple(sort(collect(...))), which is then no longer type stable in the chain of default_algorithm functions, so I am just going to revert to the generated function since I do think this is a valid use-case.

@Jutho
Copy link
Member

Jutho commented Mar 13, 2026

Ok, I quickly checked on LTS. That is indeed annoying. The one solution you did not think of is using TupleTools.sort 😄 . But I am fine with the current solution; maybe just add a comment that we can get rid of @generated when the LTS is at some point changed.

@lkdvos lkdvos merged commit 164f9e8 into main Mar 13, 2026
5 of 9 checks passed
@lkdvos lkdvos deleted the ld-typestability branch March 13, 2026 18: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