Skip to content

Commit 93ec354

Browse files
authored
Fix accidential API breakage in one() (#763)
It turns out that Rotations relies on one(M) calling the constructor of M, so transitioning to use of simliar_type in #690 broke Rotations. It's arguably a bug to call the constructor rather than calling similar_type, but let's avoid breaking compatibility for now. (It's a bug because some sets of matrices defined by a type cannot represent the multiplicative identity, for example the set of skew-symmetric matrices. StaticArrays should likely assume that generic operations like one() must devolve to a matrix type which can represent anything in GL(n,R). For example, SMatrix.)
1 parent 10c001d commit 93ec354

File tree

2 files changed

+37
-5
lines changed

2 files changed

+37
-5
lines changed

src/linalg.jl

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,30 +87,49 @@ end
8787
@inline Base.zero(a::SA) where {SA <: StaticArray} = zeros(SA)
8888
@inline Base.zero(a::Type{SA}) where {SA <: StaticArray} = zeros(SA)
8989

90+
@inline _construct_sametype(a::Type, elements) = a(elements)
91+
@inline _construct_sametype(a, elements) = typeof(a)(elements)
92+
9093
@inline one(m::StaticMatrixLike) = _one(Size(m), m)
9194
@inline one(::Type{SM}) where {SM<:StaticMatrixLike}= _one(Size(SM), SM)
9295
function _one(s::Size, m_or_SM)
9396
if (length(s) != 2) || (s[1] != s[2])
9497
throw(DimensionMismatch("multiplicative identity defined only for square matrices"))
9598
end
96-
_scalar_matrix(s, m_or_SM, one(_eltype_or(m_or_SM, Float64)))
99+
λ = one(_eltype_or(m_or_SM, Float64))
100+
_construct_sametype(m_or_SM, _scalar_matrix_elements(s, λ))
101+
# TODO: Bring back the use of _construct_similar here:
102+
# _construct_similar(m_or_SM, s, _scalar_matrix_elements(s, λ))
103+
#
104+
# However, because _construct_similar uses `similar_type`, it will be
105+
# breaking for some StaticMatrix types (in particular Rotations.RotMatrix)
106+
# which must have similar_type return a general type able to hold any
107+
# matrix in the full general linear group.
108+
#
109+
# (Generally we're stuck here and things like RotMatrix really need to
110+
# override one() and zero() themselves: on the one hand, one(RotMatrix)
111+
# should return a RotMatrix, but zero(RotMatrix) can not be a RotMatrix.
112+
# The best StaticArrays can do is to use similar_type to return an SMatrix
113+
# for both of these, and let the more specialized library define the
114+
# correct algebraic properties.)
97115
end
98116

99117
# StaticMatrix(I::UniformScaling)
100-
(::Type{SM})(I::UniformScaling) where {SM<:StaticMatrix} = _scalar_matrix(Size(SM), SM, I.λ)
118+
(::Type{SM})(I::UniformScaling) where {SM<:StaticMatrix} =
119+
SM(_scalar_matrix_elements(Size(SM), I.λ))
101120
# The following oddity is needed if we want `SArray{Tuple{2,3}}(I)` to work
102121
# because we do not have `SArray{Tuple{2,3}} <: StaticMatrix`.
103122
(::Type{SM})(I::UniformScaling) where {SM<:(StaticArray{Tuple{N,M}} where {N,M})} =
104-
_scalar_matrix(Size(SM), SM, I.λ)
123+
SM(_scalar_matrix_elements(Size(SM), I.λ))
105124

106125
# Construct a matrix with the scalar λ on the diagonal and zeros off the
107126
# diagonal. The matrix can be non-square.
108-
@generated function _scalar_matrix(s::Size{S}, m_or_SM, λ) where {S}
127+
@generated function _scalar_matrix_elements(s::Size{S}, λ) where {S}
109128
elements = Symbol[i == j ? : :λzero for i in 1:S[1], j in 1:S[2]]
110129
return quote
111130
$(Expr(:meta, :inline))
112131
λzero = zero(λ)
113-
_construct_similar(m_or_SM, s, tuple($(elements...)))
132+
tuple($(elements...))
114133
end
115134
end
116135

test/linalg.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ using StaticArrays, Test, LinearAlgebra
22

33
using LinearAlgebra: checksquare
44

5+
# For one() test
6+
struct RotMat2 <: StaticMatrix{2,2,Float64}
7+
elements::NTuple{4,Float64}
8+
end
9+
Base.getindex(m::RotMat2, i::Int) = getindex(m.elements, i)
10+
# Rotation matrices must be unitary so `similar_type` has to return an SMatrix.
11+
StaticArrays.similar_type(::Union{RotMat2,Type{RotMat2}}) = SMatrix{2,2,Float64,4}
12+
513
@testset "Linear algebra" begin
614

715
@testset "SArray as a (mathematical) vector space" begin
@@ -103,6 +111,11 @@ using LinearAlgebra: checksquare
103111
@test @inferred(one(MMatrix{2}))::MMatrix == @MMatrix [1.0 0.0; 0.0 1.0]
104112

105113
@test_throws DimensionMismatch one(MMatrix{2,4})
114+
115+
@test one(RotMat2) isa RotMat2
116+
@test one(RotMat2) == SA[1 0; 0 1]
117+
# TODO: See comment in _one.
118+
@test_broken one(RotMat2) isa SMatrix{2,2,Float64}
106119
end
107120

108121
@testset "cross()" begin

0 commit comments

Comments
 (0)