Skip to content

Commit 1d31b07

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 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); 1.945673 seconds (45.22 M allocations: 2.261 GiB, 20.60% gc time, 4.02% compilation time) julia> @time det(m15); 1.991062 seconds (45.22 M allocations: 2.261 GiB, 23.74% gc time) julia> @time det(m16); 4.596664 seconds (106.67 M allocations: 5.324 GiB, 22.65% gc time) julia> @time det(m16); 4.648503 seconds (106.67 M allocations: 5.324 GiB, 22.66% gc time) ``` The above REPL session is with this commit applied, and with all other recent PRs of mine applied, to MultivariatePolynomials.jl, DynamicPolynomials.jl, and MutableArithmetics.jl. The same computation with MultivariatePolynomials v0.5.3 ran for multiple minutes before I decided to just kill it. Depends on #285. Fixes #281.
1 parent fcb1b29 commit 1d31b07

File tree

2 files changed

+79
-13
lines changed

2 files changed

+79
-13
lines changed

src/det.jl

+69-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,72 @@
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+
get_elem = let A = A
8+
(i, j) -> LinearAlgebra.det(A[i, j])
9+
end
10+
r = first(size(A))
11+
if 1 < r
12+
total = LinearAlgebra.det(zero(T))
13+
for i in Base.OneTo(r)
14+
a = get_elem(i, 1)
15+
if !iszero(a)
16+
ii = Base.OneTo(r) .!= i
17+
jj = 2:r
18+
B = A[ii, jj]
19+
x = det_impl(B)
20+
x = MA.operate!!(*, x, a)
21+
if iseven(i)
22+
x = MA.operate!!(-, x)
23+
end
24+
total = MA.operate!!(+, total, x)
25+
end
26+
end
27+
total
28+
elseif isone(r)
29+
MA.copy_if_mutable(get_elem(1, 1))
30+
else
31+
error("unexpected")
32+
end
33+
end
34+
35+
collect_if_not_already_matrix(m::Matrix) = m
36+
collect_if_not_already_matrix(m::AbstractMatrix) = collect(m)
37+
38+
function det_impl_outer(m::AbstractMatrix{T}) where {T}
39+
if 0 < LinearAlgebra.checksquare(m)
40+
det_impl(collect_if_not_already_matrix(m))
1041
else
11-
return M[1, 1]
42+
LinearAlgebra.det(one(T))
1243
end
1344
end
45+
46+
# Determinants of narrow integer type: `LinearAlgebra` seems to
47+
# promote these to `Float64` to prevent them from overflowing. We
48+
# instead promote to `BigInt` to keep things exact. In the case of
49+
# `Bool` we also need to promote for type stability.
50+
51+
const NarrowIntegerTypes =
52+
Union{Bool,UInt8,Int8,UInt16,Int16,UInt32,Int32,UInt64,Int64,UInt128,Int128}
53+
54+
const NarrowIntegerPolynomialLike =
55+
AbstractPolynomialLike{T} where {T<:NarrowIntegerTypes}
56+
57+
promote_if_narrow(m::AbstractMatrix{<:AbstractPolynomialLike}) = m
58+
59+
function promote_if_narrow(m::AbstractMatrix{<:NarrowIntegerPolynomialLike})
60+
return map((p -> polynomial(p, BigInt)), m)
61+
end
62+
63+
# For type stability, we want to promote termlikes to polynomiallikes
64+
# before attempting to calculate the determinant.
65+
promote_if_termlike(m::AbstractMatrix{<:AbstractPolynomialLike}) = m
66+
promote_if_termlike(m::AbstractMatrix{<:AbstractTermLike}) = map(polynomial, m)
67+
68+
promote_if_necessary(m) = promote_if_termlike(promote_if_narrow(m))
69+
70+
function LinearAlgebra.det(m::AbstractMatrix{<:AbstractPolynomialLike})
71+
return det_impl_outer(promote_if_necessary(m))
72+
end

test/det.jl

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
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 in (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])) ==
13+
-x * y^2 + 2 * x * y - x
714
end

0 commit comments

Comments
 (0)