Skip to content

Commit 2889bfb

Browse files
committed
LinearAlgebra.det improvements
Performance improvements, support for more types. Still broken for `LinearAlgebra.Symmetric` polynomial matrices, producing a `MethodError` because of a missing `oneunit` method. This, however, seems like a separate matter that would better be addressed by a separate pull request. Performance comparison: ```julia-repl julia> versioninfo() Julia Version 1.11.0-DEV.972 Commit 9884e447e79 (2023-11-23 16:16 UTC) Build Info: Official https://julialang.org/ release Platform Info: OS: Linux (x86_64-linux-gnu) CPU: 8 × AMD Ryzen 3 5300U with Radeon Graphics WORD_SIZE: 64 LLVM: libLLVM-15.0.7 (ORCJIT, znver2) Threads: 11 on 8 virtual cores Environment: JULIA_NUM_PRECOMPILE_TASKS = 3 JULIA_PKG_PRECOMPILE_AUTO = 0 julia> using LinearAlgebra, DynamicPolynomials julia> function f(n) @PolyVar a b c d e diagm( -2 => fill(a, n - 2), -1 => fill(b, n - 1), 0 => fill(c, n), 2 => fill(e, n - 2), 1 => fill(d, n - 1), ) end f (generic function with 1 method) julia> const m15 = f(15); julia> const m16 = f(16); julia> @time det(m15); 2.489404 seconds (46.04 M allocations: 2.244 GiB, 18.91% gc time, 13.04% compilation time) julia> @time det(m15); 2.231880 seconds (45.94 M allocations: 2.238 GiB, 21.50% gc time) julia> @time det(m16); 5.362580 seconds (107.70 M allocations: 5.243 GiB, 23.50% gc time) julia> @time det(m16); 5.405048 seconds (107.70 M allocations: 5.243 GiB, 23.65% gc time) ``` The above REPL session is with this commit applied. The same computation with MultivariatePolynomials v0.5.3 ran for multiple minutes before I decided to just kill it. Fixes #281
1 parent fcb1b29 commit 2889bfb

File tree

2 files changed

+79
-13
lines changed

2 files changed

+79
-13
lines changed

src/det.jl

+70-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,73 @@
1-
function LinearAlgebra.det(M::Matrix{<:AbstractPolynomialLike})
2-
m = size(M)[1]
3-
if m > 2
4-
return sum(
5-
(-1)^(i - 1) * M[i, 1] * LinearAlgebra.det(M[1:end.!=i, 2:end]) for
6-
i in 1:m
7-
)
8-
elseif m == 2
9-
return M[1, 1] * M[2, 2] - M[2, 1] * M[1, 2]
1+
# Scalar determinant, used for recursive computation of the determinant
2+
LinearAlgebra.det(p::AbstractPolynomialLike{<:Number}) = p
3+
4+
# Matrix determinant by cofactor expansion, adapted from
5+
# `LinearAlgebraX.cofactor_det`.
6+
function det_impl(A::AbstractMatrix{T}) where {T}
7+
r = first(size(A))
8+
if 1 < r
9+
total = LinearAlgebra.det(zero(T))
10+
for i Base.OneTo(r)
11+
a = LinearAlgebra.det(A[i, 1])
12+
if !iszero(a)
13+
ii = Base.OneTo(r) .!= i
14+
jj = 2:r
15+
B = A[ii, jj]
16+
x = det_impl(B)
17+
# XXX: should be faster but doesn't work:
18+
# a = MA.operate!!(*, a, x)
19+
a *= x
20+
if iseven(i)
21+
# XXX: not implemented yet:
22+
# a = MA.operate!!(-, a)
23+
a = -a
24+
end
25+
total = MA.operate!!(+, total, a)
26+
end
27+
end
28+
total
29+
elseif isone(r)
30+
LinearAlgebra.det(A[1, 1])
1031
else
11-
return M[1, 1]
32+
error("unexpected")
1233
end
1334
end
35+
36+
collect_if_not_already_matrix(m::Matrix) = m
37+
collect_if_not_already_matrix(m::AbstractMatrix) = collect(m)
38+
39+
function det_impl_outer(m::AbstractMatrix{T}) where {T}
40+
if 0 < LinearAlgebra.checksquare(m)
41+
det_impl(collect_if_not_already_matrix(m))
42+
else
43+
LinearAlgebra.det(one(T))
44+
end
45+
end
46+
47+
# Determinants of narrow integer type: `LinearAlgebra` seems to
48+
# promote these to `Float64` to prevent them from overflowing. We
49+
# instead promote to `BigInt` to keep things exact. In the case of
50+
# `Bool` we also need to promote for type stability.
51+
52+
const NarrowIntegerTypes = Union{
53+
Bool, UInt8, Int8, UInt16, Int16, UInt32, Int32, UInt64, Int64,
54+
UInt128, Int128,
55+
}
56+
57+
const NarrowIntegerPolynomialLike =
58+
AbstractPolynomialLike{T} where {T<:NarrowIntegerTypes}
59+
60+
promote_if_narrow(m::AbstractMatrix{<:AbstractPolynomialLike}) = m
61+
62+
promote_if_narrow(m::AbstractMatrix{<:NarrowIntegerPolynomialLike}) =
63+
map((p -> polynomial(p, BigInt)), m)
64+
65+
# For type stability, we want to promote termlikes to polynomiallikes
66+
# before attempting to calculate the determinant.
67+
promote_if_termlike(m::AbstractMatrix{<:AbstractPolynomialLike}) = m
68+
promote_if_termlike(m::AbstractMatrix{<:AbstractTermLike}) = map(polynomial, m)
69+
70+
promote_if_necessary(m) = promote_if_termlike(promote_if_narrow(m))
71+
72+
LinearAlgebra.det(m::AbstractMatrix{<:AbstractPolynomialLike}) =
73+
det_impl_outer(promote_if_necessary(m))

test/det.jl

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
@testset "Det" begin
22
Mod.@polyvar x y
33

4-
@test det([x 1; y 1]) == x - y
5-
@test det([x 1; x 1]) == 0
6-
@test det([x 1 1 1; x y 1 2; 0 0 0 1; x 0 y 0]) == -x * y^2 + 2 * x * y - x
4+
@testset "zero-one $T" for T (Bool, Int, Float64, BigInt, BigFloat)
5+
z = zero(T)
6+
o = one(T)
7+
@test (@inferred det([x o; y o])) == x - y
8+
@test (@inferred det([x o; x o])) == 0
9+
@test (@inferred det([x+y y; z y])) == (x+y)*y
10+
end
11+
12+
@test (@inferred det([x 1 1 1; x y 1 2; 0 0 0 1; x 0 y 0])) == -x * y^2 + 2 * x * y - x
713
end

0 commit comments

Comments
 (0)