diff --git a/src/Rotations.jl b/src/Rotations.jl index 8455832c..3d19b373 100644 --- a/src/Rotations.jl +++ b/src/Rotations.jl @@ -22,6 +22,7 @@ include("principal_value.jl") include("rodrigues_params.jl") include("error_maps.jl") include("rotation_error.jl") +include("eigen.jl") include("deprecated.jl") export diff --git a/src/eigen.jl b/src/eigen.jl new file mode 100644 index 00000000..56622162 --- /dev/null +++ b/src/eigen.jl @@ -0,0 +1,42 @@ +# 3D +function LinearAlgebra.eigvals(R::Rotation{3}) + θ = rotation_angle(R) + return SVector(exp(-θ*im), exp(θ*im), 1) +end + +function LinearAlgebra.eigvecs(R::Rotation{3}) + n3 = normalize(rotation_axis(R)) + n1 = normalize(perpendicular_vector(n3)) + n2 = normalize(n3×n1) + v1 = normalize(n1 + n2*im) + v2 = normalize(n1*im + n2) + v3 = n3 + return hcat(v1,v2,v3) +end + +function LinearAlgebra.eigen(R::Rotation{3}) + return eigen(AngleAxis(R)) +end + +function LinearAlgebra.eigen(R::AngleAxis) + λs = eigvals(R) + vs = eigvecs(R) + return Eigen(λs, vs) +end + + +# 2D +function LinearAlgebra.eigvals(R::Rotation{2}) + θ = rotation_angle(R) + return SVector(exp(-θ*im), exp(θ*im)) +end + +function LinearAlgebra.eigvecs(R::Rotation{2}) + return @SMatrix [1/√2 1/√2;im/√2 -im/√2] +end + +function LinearAlgebra.eigen(R::Rotation{2}) + λs = eigvals(R) + vs = eigvecs(R) + return Eigen(λs, vs) +end diff --git a/test/eigen.jl b/test/eigen.jl new file mode 100644 index 00000000..2caa7fe2 --- /dev/null +++ b/test/eigen.jl @@ -0,0 +1,57 @@ +@testset "Eigen_3D" begin + all_types = (RotMatrix{3}, AngleAxis, RotationVec, + UnitQuaternion, RodriguesParam, MRP, + RotXYZ, RotYZX, RotZXY, RotXZY, RotYXZ, RotZYX, + RotXYX, RotYZY, RotZXZ, RotXZX, RotYXY, RotZYZ, + RotX, RotY, RotZ, + RotXY, RotYZ, RotZX, RotXZ, RotYX, RotZY) + oneaxis_types = (RotX, RotY, RotZ) + + @testset "$(T)" for T in all_types, F in (one, rand) + R = F(T) + λs = eigvals(R) + vs = eigvecs(R) + E = eigen(R) + v1 = vs[:,1] + v2 = vs[:,2] + v3 = vs[:,3] + @test R * vs ≈ transpose(λs) .* vs + @test norm(v1) ≈ 1 + @test norm(v2) ≈ 1 + @test norm(v3) ≈ 1 + @test E.values == λs + @test E.vectors == vs + if !(T in oneaxis_types) && VERSION ≥ v"1.2" + # If the rotation angle is in [0°, 180°], then the eigvals will be equal. + # Note that the randomized RotX (and etc.) have rotation angle in [0°, 360°]. + # This needs Julia(≥1.2) to get sorted eigenvalues in a canonical order + # See https://github.com/JuliaLang/julia/pull/21598 + @test eigvals(R) ≈ eigvals(collect(R)) + end + end +end + +@testset "Eigen_2D" begin + all_types = (RotMatrix{2}, Angle2d) + + @testset "$(T)" for T in all_types, θ in 0.0:0.1:π + R = T(Angle2d(θ)) + λs = eigvals(R) + vs = eigvecs(R) + E = eigen(R) + v1 = vs[:,1] + v2 = vs[:,2] + @test R * vs ≈ transpose(λs) .* vs + @test norm(v1) ≈ 1 + @test norm(v2) ≈ 1 + @test E.values == λs + @test E.vectors == vs + if VERSION ≥ v"1.2" + # If the rotation angle θ is in [0°, 180°], then the eigvals will be equal. + # Note that the randomized RotX (and etc.) have rotation angle in [0°, 360°]. + # This needs Julia(≥1.2) to get sorted eigenvalues in a canonical order + # See https://github.com/JuliaLang/julia/pull/21598 + @test eigvals(R) ≈ eigvals(collect(R)) + end + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 967e52fd..33904e2f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -28,6 +28,7 @@ include("rodrigues_params.jl") include("quatmaps.jl") include("rotation_error.jl") include("distribution_tests.jl") +include("eigen.jl") include("deprecated.jl") include(joinpath(@__DIR__, "..", "perf", "runbenchmarks.jl"))