Skip to content

Commit 8b2133d

Browse files
committed
Merge branch 'triangular' of https://github.com/bshall/StaticArrays.jl into triangular
2 parents 428188d + 794951a commit 8b2133d

File tree

11 files changed

+186
-36
lines changed

11 files changed

+186
-36
lines changed

src/SUnitRange.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ end
1919
@pure SUnitRange(a::Int, b::Int) = SUnitRange{a, max(0, b - a + 1)}()
2020

2121
@pure @propagate_inbounds function getindex(x::SUnitRange{Start, L}, i::Int) where {Start, L}
22-
@boundscheck if i < Start || i >= (Start + L)
22+
@boundscheck if i < 1 || i > L
2323
throw(BoundsError(x, i))
2424
end
2525
return Start + i - 1

src/det.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ end
2424
return vecdot(x0, cross(x1, x2))
2525
end
2626

27-
@generated function _det{S,T}(::Size{S}, A::StaticMatrix{T})
27+
@generated function _det{S}(::Size{S}, A::StaticMatrix)
2828
if S[1] != S[2]
2929
throw(DimensionMismatch("matrix is not square"))
3030
end
3131
return quote # Implementation from Base
3232
@_inline_meta
33+
T = eltype(A)
3334
T2 = typeof((one(T)*zero(T) + zero(T))/one(T))
3435
if istriu(A) || istril(A)
3536
return convert(T2, det(UpperTriangular(A))) # Is this a Julia bug that a convert is not type stable??

src/expm.jl

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,28 @@ end
4040
(newtype)((m11, m21, m12, m22))
4141
end
4242

43-
# TODO add complex valued expm
43+
@inline function _expm(::Size{(2,2)}, A::StaticMatrix{<:Any,<:Any,<:Complex})
44+
T = typeof(exp(zero(eltype(A))))
45+
newtype = similar_type(A,T)
46+
47+
@inbounds a = A[1]
48+
@inbounds c = A[2]
49+
@inbounds b = A[3]
50+
@inbounds d = A[4]
51+
52+
z = sqrt((a - d)*(a - d) + 4*b*c )
53+
e = exp((a + d - z)/2)
54+
f = exp((a + d + z)/2)
55+
zr = inv(z)
56+
57+
m11 = (-e*(a - d - z) + f*(a - d + z)) * zr/2
58+
m12 = (f-e) * b * zr
59+
m21 = (f-e) * c * zr
60+
m22 = (-e*(-a + d - z) + f*(-a + d + z)) * zr/2
61+
62+
(newtype)((m11, m21, m12, m22))
63+
end
64+
4465
# TODO add special case for 3x3 matrices
4566

4667
@inline _expm(s::Size, A::StaticArray) = s(Base.expm(Array(A)))

src/indexing.jl

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,13 @@ end
5454
## Indexing utilities ##
5555
#########################
5656

57-
@pure tail(::Type{Size{S}}) where {S} = Size{Base.tail(S)}
58-
@inline tail(::S) where {S<:Size} = tail(S)()
59-
@inline tail(s::Size{()}) = s
60-
61-
@inline index_sizes(s::Size) = ()
62-
@inline index_sizes(s::Size, ::Int, inds...) = (Size(), index_sizes(tail(s), inds...)...)
63-
@inline index_sizes(s::Size, a::StaticArray, inds...) = (Size(a), index_sizes(tail(s), inds...)...)
64-
@inline index_sizes(s::Size, ::Colon, inds...) = (Size(s[1]), index_sizes(tail(s), inds...)...)
57+
@pure unpack_size(::Type{Size{S}}) where {S} = map(Size, S)
58+
59+
@inline index_size(::Size, ::Int) = Size()
60+
@inline index_size(::Size, a::StaticArray) = Size(a)
61+
@inline index_size(s::Size, ::Colon) = s
62+
63+
@inline index_sizes(::S, inds...) where {S<:Size} = map(index_size, unpack_size(S), inds)
6564

6665
@inline index_sizes() = ()
6766
@inline index_sizes(::Int, inds...) = (Size(), index_sizes(inds...)...)

src/inv.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,34 @@ end
3030
@inbounds return newtype((y0[1], y1[1], y2[1], y0[2], y1[2], y2[2], y0[3], y1[3], y2[3]))
3131
end
3232

33+
Base.@pure function splitrange(r::SUnitRange)
34+
mid = (first(r) + last(r)) ÷ 2
35+
(SUnitRange(first(r), mid), SUnitRange(mid+1, last(r)))
36+
end
37+
38+
@noinline function _inv(::Size{(4,4)}, A)
39+
# Partition matrix into four 2x2 blocks. For 4x4 matrices this seems to be
40+
# more stable than directly using the adjugate expansion.
41+
# See http://www.freevec.org/function/inverse_matrix_4x4_using_partitioning
42+
#
43+
# TODO: This decomposition works in higher dimensions, but numerical
44+
# stability doesn't seem good for badly conditioned matrices. Can be
45+
# fixed?
46+
(i1,i2) = splitrange(SUnitRange(1,4))
47+
@inbounds P = A[i1,i1]
48+
@inbounds Q = A[i1,i2]
49+
@inbounds R = A[i2,i1]
50+
@inbounds S = A[i2,i2]
51+
invP = inv(P)
52+
invP_Q = invP*Q
53+
S2 = inv(S - R*invP_Q)
54+
R2 = -S2*(R*invP)
55+
Q2 = -invP_Q*S2
56+
P2 = invP - invP_Q*R2
57+
[[P2 Q2];
58+
[R2 S2]]
59+
end
60+
3361
@inline function _inv(::Size, A)
3462
T = eltype(A)
3563
S = typeof((one(T)*zero(T) + zero(T))/one(T))

test/SUnitRange.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
@test length(StaticArrays.SUnitRange(1,3)) === 3
33
@test length(StaticArrays.SUnitRange(1,-10)) === 0
44

5+
@test_throws BoundsError StaticArrays.SUnitRange(2,4)[0]
6+
@test StaticArrays.SUnitRange(2,4)[1] === 2
57
@test StaticArrays.SUnitRange(2,4)[2] === 3
6-
7-
@test_throws BoundsError StaticArrays.SUnitRange(1,3)[4]
8+
@test StaticArrays.SUnitRange(2,4)[3] === 4
9+
@test_throws BoundsError StaticArrays.SUnitRange(2,4)[4]
10+
811
@test_throws Exception StaticArrays.SUnitRange{1, -1}()
912
@test_throws TypeError StaticArrays.SUnitRange{1, 1.5}()
1013
end

test/det.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#triu/tril
88
@test det(@SMatrix [1 2; 0 3]) === 3
99
@test det(@SMatrix [1 2 3 4; 0 5 6 7; 0 0 8 9; 0 0 0 10]) === 400.0
10+
@test @inferred(det(ones(SMatrix{10,10,Complex{Float64}}))) == 0
1011

1112
# Unsigned specializations
1213
@test det(@SMatrix [0x00 0x01; 0x01 0x00])::Int8 == -1

test/expm.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
@test expm(@SMatrix [5 2; -2 1])::SMatrix expm([5 2; -2 1])
44
@test expm(@SMatrix [4 2; -2 1])::SMatrix expm([4 2; -2 1])
55
@test expm(@SMatrix [4 2; 2 1])::SMatrix expm([4 2; 2 1])
6+
@test expm(@SMatrix [ -3+1im -1+2im;-4-5im 5-2im])::SMatrix expm(Complex{Float64}[ -3+1im -1+2im;-4-5im 5-2im])
67
@test expm(@SMatrix [1 2 0; 2 1 0; 0 0 1])::SizedArray{Tuple{3,3}} expm([1 2 0; 2 1 0; 0 0 1])
78
end

test/inv.jl

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,125 @@
1+
using StaticArrays, Base.Test
2+
3+
"""
4+
Create an almost singular `Matrix{Float64}` of size `N×N`.
5+
6+
The first `rank` columns are chosen randomly, followed by `N-rank` linear
7+
combinations with random weights to create an `N×N` singular matrix `S`.
8+
A noise matrix of norm `ϵ*norm(S)` is added.
9+
10+
The condition number of this matrix should be approximately ϵ/eps(Float64)
11+
"""
12+
function almost_singular_matrix(N, rank, ϵ)
13+
B = rand(N,rank)
14+
weights = rand(rank, N-rank) ./ rank
15+
S = [B B*weights]
16+
S = S ./ norm(S)
17+
noise = rand(N,N)
18+
S + ϵ*norm(S)/norm(noise) * noise
19+
end
20+
121
@testset "Matrix inverse" begin
222
@test inv(@SMatrix [2])::SMatrix @SMatrix [0.5]
323
@test inv(@SMatrix [1 2; 2 1])::SMatrix [-1/3 2/3; 2/3 -1/3]
424
@test inv(@SMatrix [1 2 0; 2 1 0; 0 0 1])::SMatrix [-1/3 2/3 0; 2/3 -1/3 0; 0 0 1]
5-
m = randn(Float64, 4,4) + eye(4) # well conditioned
6-
@test inv(SMatrix{4,4}(m))::StaticMatrix inv(m)
725

26+
m = randn(Float64, 10,10) + eye(10) # well conditioned
27+
@test inv(SMatrix{10,10}(m))::StaticMatrix inv(m)
28+
29+
30+
# Unsigned versions
831
@test inv(@SMatrix [0x01 0x02; 0x02 0x01])::SMatrix [-1/3 2/3; 2/3 -1/3]
932
@test inv(@SMatrix [0x01 0x02 0x00; 0x02 0x01 0x00; 0x00 0x00 0x01])::SMatrix [-1/3 2/3 0; 2/3 -1/3 0; 0 0 1]
10-
33+
1134
m = triu(randn(Float64, 4,4) + eye(4))
1235
@test inv(SMatrix{4,4}(m))::StaticMatrix inv(m)
1336
m = tril(randn(Float64, 4,4) + eye(4))
1437
@test inv(SMatrix{4,4}(m))::StaticMatrix inv(m)
1538
end
39+
40+
41+
@testset "Matrix inverse 4x4" begin
42+
# A random well-conditioned matrix generated with `rand(1:10,4,4)`
43+
m = Float64[8 4 3 1;
44+
9 1 9 8;
45+
5 0 2 1;
46+
7 5 7 2]
47+
sm = SMatrix{4,4}(m)
48+
@test isapprox(inv(sm)::StaticMatrix, inv(m), rtol=2e-15)
49+
50+
# Poorly conditioned matrix; almost_singular_matrix(4, 3, 1e-7)
51+
m = [
52+
2.83056817904263402e-01 1.26822318692296848e-01 2.59665505365002547e-01 1.24524798964590747e-01
53+
4.29869029098094768e-01 2.29977256378012789e-03 4.63841570354149135e-01 1.51975221831027241e-01
54+
2.33883312850939995e-01 7.69469213907006122e-02 3.16525493661056589e-01 1.06181613512460249e-01
55+
1.63327582123091175e-01 1.70552365412100865e-01 4.55720450934200327e-01 1.23299968650232419e-01
56+
]
57+
sm = SMatrix{4,4}(m)
58+
@test norm(Matrix(sm*inv(sm) - eye(4))) < 10*norm(m*inv(m) - eye(4))
59+
@test norm(Matrix(inv(sm)*sm - eye(4))) < 10*norm(inv(m)*m - eye(4))
60+
61+
# Poorly conditioned matrix, generated by fuzz testing with
62+
# almost_singular_matrix(N, 3, 1e-7)
63+
# Case where the 4x4 algorithm fares badly compared to Base inv
64+
m = [1.80589503332920231e-02 2.37595944073211218e-01 3.80770755039433806e-01 5.70474286930408372e-02
65+
2.17494123374967520e-02 2.94069278741667661e-01 2.57334651147732463e-01 4.02348559939922357e-02
66+
3.42945636322714076e-01 3.29329508494837497e-01 2.25635541033857107e-01 6.03912636987153917e-02
67+
4.77828437366344727e-01 4.86406974015710591e-01 1.95415684569693188e-01 6.74775080892497797e-02]
68+
sm = SMatrix{4,4}(m)
69+
@test_broken norm(Matrix(inv(sm)*sm - eye(4))) < 10*norm(inv(m)*m - eye(4))
70+
@test_broken norm(Matrix(sm*inv(sm) - eye(4))) < 10*norm(m*inv(m) - eye(4))
71+
end
72+
73+
74+
#-------------------------------------------------------------------------------
75+
# More comprehensive but qualitiative testing for inv() accuracy
76+
#=
77+
using PyPlot
78+
79+
inv_residual(A::AbstractMatrix) = norm(A*inv(A) - eye(size(A,1)))
80+
81+
"""
82+
plot_residuals(N, rank, ϵ)
83+
84+
Plot `inv_residual(::StaticMatrix)` vs `inv_residual(::Matrix)`
85+
86+
"""
87+
function plot_residuals(N, rank, ϵ)
88+
A_residuals = []
89+
SA_residuals = []
90+
for i=1:10000
91+
A = almost_singular_matrix(N, rank, ϵ)
92+
SA = SMatrix{N,N}(A)
93+
SA_residual = norm(Matrix(SA*inv(SA) - eye(N)))
94+
A_residual = norm(A*inv(A) - eye(N))
95+
push!(SA_residuals, SA_residual)
96+
push!(A_residuals, A_residual)
97+
#= if SA_residual/A_residual > 10000 =#
98+
#= @printf("%10e %.4f\n", SA_residual, SA_residual/A_residual) =#
99+
#= println("[") =#
100+
#= for i=1:4 =#
101+
#= for j=1:4 =#
102+
#= @printf("%.17e ", A[i,j]) =#
103+
#= end =#
104+
#= println() =#
105+
#= end =#
106+
#= println("]") =#
107+
#= end =#
108+
end
109+
loglog(A_residuals, SA_residuals, ".", markersize=1.5)
110+
end
111+
112+
# Plot the accuracy of inv implementations for almost singular matrices of
113+
# various rank
114+
clf()
115+
N = 4
116+
title("inv() accuracy for poorly conditioned $(N)x$(N) - Base vs block decomposition")
117+
labels = []
118+
for i in N:-1:1
119+
plot_residuals(N, i, 1e-7)
120+
push!(labels, "rank $i")
121+
end
122+
xlabel("Residual norm: `inv(::Array)`")
123+
ylabel("Residual norm: `inv(::StaticArray)`")
124+
legend(labels)
125+
=#

test/runtests.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ include("matrix_multiply.jl")
2424
include("triangular.jl")
2525
include("det.jl")
2626
include("inv.jl")
27-
include("solve.jl") # Strange inference / world-age error
27+
include("solve.jl")
2828
include("eigen.jl")
2929
include("expm.jl")
3030
include("sqrtm.jl")
3131
include("chol.jl")
3232
include("deque.jl")
3333
include("io.jl")
34+
include("svd.jl")
3435
include("fixed_size_arrays.jl")

0 commit comments

Comments
 (0)