Skip to content

Commit 6d37861

Browse files
authored
Implement wrapped-SMatrix + UniformScaling (#1332)
## Context `UniformScaling` operations on structured wrappers backed by `StaticMatrix` were falling back to generic `LinearAlgebra` methods. That path uses mutable copies, which caused type leakage from `SMatrix`-backed wrappers to `MMatrix`-backed wrappers. In some cases (for example `I - Symmetric(SMatrix(...))`), it could also fail due to mutation of immutable storage. I don't think there is an easy way to solve this problem in general due to how these operations are defined in base, so instead I've just written some appropriate wrappers. ## What this changes This PR adds `UniformScaling` specialisations for structured wrappers over `StaticMatrix` in `src/linalg.jl`: - `Symmetric` - `Hermitian` - `UpperTriangular` - `LowerTriangular` - `UnitUpperTriangular` (result type `UpperTriangular`) - `UnitLowerTriangular` (result type `LowerTriangular`) I only implement methods for `A + J` and `J - A` for brevity, with `uniformscaling.jl:L156` handling the reverse argument orders. I include a special case for the some of Hermitian + complex UniformScaling which removes the wrapper to be type stable, consistent with base LinearAlgebra.jl. ## Tests I've extended `test/linalg.jl` (`Interaction with UniformScaling`) to cover all of the cases mentioned.
1 parent 58bf1e1 commit 6d37861

3 files changed

Lines changed: 94 additions & 1 deletion

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "StaticArrays"
22
uuid = "90137ffa-7385-5640-81b9-e52037218182"
3-
version = "1.9.16"
3+
version = "1.9.17"
44

55
[deps]
66
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

src/linalg.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,50 @@ import Base: +, -, *, /, \
4040
end
4141
end
4242

43+
@inline _plus_uniform_parent(A, λ) = _plus_uniform(Size(A), parent(A), λ)
44+
@inline _minus_uniform_parent(A, λ) = _plus_uniform(Size(A), -parent(A), λ)
45+
@inline _sym_uplo(A) = A.uplo == 'U' ? :U : :L
46+
47+
# Self-adjoint wrappers over StaticMatrix with UniformScaling
48+
@inline +(A::Symmetric{T,<:StaticMatrix{N,M,T}}, J::UniformScaling) where {N,M,T} =
49+
Symmetric(_plus_uniform_parent(A, J.λ), _sym_uplo(A))
50+
@inline -(J::UniformScaling, A::Symmetric{T,<:StaticMatrix{N,M,T}}) where {N,M,T} =
51+
Symmetric(_minus_uniform_parent(A, J.λ), _sym_uplo(A))
52+
53+
@inline +(A::Hermitian{T,<:StaticMatrix{N,M,T}}, J::UniformScaling) where {N,M,T} =
54+
Hermitian(_plus_uniform_parent(A, J.λ), _sym_uplo(A))
55+
@inline -(J::UniformScaling, A::Hermitian{T,<:StaticMatrix{N,M,T}}) where {N,M,T} =
56+
Hermitian(_minus_uniform_parent(A, J.λ), _sym_uplo(A))
57+
58+
# Lose Hermitian wrapper when adding complex UniformScaling, in-line with Base behavior
59+
@inline function +(A::Hermitian{T,<:StaticMatrix{N,M,T}}, J::UniformScaling{<:Complex}) where {N,M,T}
60+
TS = Base.promote_op(+, eltype(A), typeof(J))
61+
_plus_uniform(Size(A), similar_type(A, TS)(A), J.λ)
62+
end
63+
@inline function -(J::UniformScaling{<:Complex}, A::Hermitian{T,<:StaticMatrix{N,M,T}}) where {N,M,T}
64+
TS = Base.promote_op(+, eltype(A), typeof(J))
65+
_plus_uniform(Size(A), -similar_type(A, TS)(A), J.λ)
66+
end
67+
68+
# Triangular wrappers over StaticMatrix with UniformScaling
69+
for TWR in (:UpperTriangular, :LowerTriangular)
70+
@eval begin
71+
@inline +(A::$TWR{T,<:StaticMatrix{N,M,T}}, J::UniformScaling) where {N,M,T} = $TWR(_plus_uniform_parent(A, J.λ))
72+
@inline -(J::UniformScaling, A::$TWR{T,<:StaticMatrix{N,M,T}}) where {N,M,T} = $TWR(_minus_uniform_parent(A, J.λ))
73+
end
74+
end
75+
76+
# Unit triangular wrappers over StaticMatrix with UniformScaling
77+
@inline _plus_uniform_materialized(A, λ) = _plus_uniform(Size(A), similar_type(A, promote_type(eltype(A), typeof(λ)))(A), λ)
78+
@inline _minus_uniform_materialized(A, λ) = _plus_uniform(Size(A), -similar_type(A, promote_type(eltype(A), typeof(λ)))(A), λ)
79+
80+
for (TWR1, TWR2) in ((:UnitUpperTriangular, :UpperTriangular), (:UnitLowerTriangular, :LowerTriangular))
81+
@eval begin
82+
@inline +(A::$TWR1{T,<:StaticMatrix{N,M,T}}, J::UniformScaling) where {N,M,T} = $TWR2(_plus_uniform_materialized(A, J.λ))
83+
@inline -(J::UniformScaling, A::$TWR1{T,<:StaticMatrix{N,M,T}}) where {N,M,T} = $TWR2(_minus_uniform_materialized(A, J.λ))
84+
end
85+
end
86+
4387
@inline *(a::UniformScaling, b::Union{StaticMatrix,StaticVector}) = a.λ * b
4488
@inline *(a::StaticMatrix, b::UniformScaling) = a * b.λ
4589
@inline \(a::UniformScaling, b::Union{StaticMatrix,StaticVector}) = a.λ \ b

test/linalg.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,55 @@ end
107107
@test @inferred(I * @SMatrix([0 1; 2 3])) === @SMatrix [0 1; 2 3]
108108
@test @inferred(@SMatrix([0 1; 2 3]) / I) === @SMatrix [0.0 1.0; 2.0 3.0]
109109
@test @inferred(I \ @SMatrix([0 1; 2 3])) === @SMatrix [0.0 1.0; 2.0 3.0]
110+
111+
A = @SMatrix [1.0 2.0 3.0; 4.0 5.0 6.0; 7.0 8.0 9.0]
112+
A_dynamic = Matrix(A)
113+
114+
for WR in (Symmetric, Hermitian)
115+
B = WR(A, :L)
116+
B_dynamic = WR(A_dynamic, :L)
117+
118+
@test @inferred(B + I) isa WR{Float64,SMatrix{3,3,Float64,9}}
119+
@test @inferred(I + B) isa WR{Float64,SMatrix{3,3,Float64,9}}
120+
@test @inferred(B - I) isa WR{Float64,SMatrix{3,3,Float64,9}}
121+
@test @inferred(I - B) isa WR{Float64,SMatrix{3,3,Float64,9}}
122+
@test (B + I).uplo == 'L'
123+
@test (I - B).uplo == 'L'
124+
125+
@test B + I == B_dynamic + I
126+
@test I + B == I + B_dynamic
127+
@test B - I == B_dynamic - I
128+
@test I - B == I - B_dynamic
129+
end
130+
131+
B = Hermitian(complex.(A), :U)
132+
B_dynamic = Hermitian(complex.(A_dynamic), :U)
133+
134+
# Hermitian wrapper should be dropped iff the UniformScaling is complex, in-line with Base behavior
135+
@test @inferred(B + I) isa Hermitian{ComplexF64,SMatrix{3,3,ComplexF64,9}}
136+
@test @inferred(I - B) isa Hermitian{ComplexF64,SMatrix{3,3,ComplexF64,9}}
137+
@test @inferred(B + (1 + 2im)I) isa SMatrix{3,3,ComplexF64,9}
138+
@test @inferred((1 + 2im)I - B) isa SMatrix{3,3,ComplexF64,9}
139+
@test B + (1 + 2im)I == B_dynamic + (1 + 2im)I
140+
@test (1 + 2im)I - B == (1 + 2im)I - B_dynamic
141+
142+
for (WR1, WR2) in ((UpperTriangular, UpperTriangular),
143+
(LowerTriangular, LowerTriangular),
144+
(UnitUpperTriangular, UpperTriangular),
145+
(UnitLowerTriangular, LowerTriangular))
146+
B = WR1(A)
147+
B_dynamic = WR1(A_dynamic)
148+
149+
@test @inferred(B + I) isa WR2{Float64,SMatrix{3,3,Float64,9}}
150+
@test @inferred(I + B) isa WR2{Float64,SMatrix{3,3,Float64,9}}
151+
@test @inferred(B - I) isa WR2{Float64,SMatrix{3,3,Float64,9}}
152+
@test @inferred(I - B) isa WR2{Float64,SMatrix{3,3,Float64,9}}
153+
154+
@test B + I == B_dynamic + I
155+
@test I + B == I + B_dynamic
156+
@test B - I == B_dynamic - I
157+
@test I - B == I - B_dynamic
158+
end
110159
end
111160

112161
@testset "Constructors from UniformScaling" begin

0 commit comments

Comments
 (0)