From bd42579a28368c5ee1ac5e1958153511576accef Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Fri, 12 Nov 2021 15:10:44 +0100 Subject: [PATCH 1/8] added a second type parameter for angular rotations --- src/core_types.jl | 10 +++++----- src/euler_types.jl | 36 +++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/core_types.jl b/src/core_types.jl index 4c67926d..3b866ad1 100644 --- a/src/core_types.jl +++ b/src/core_types.jl @@ -150,17 +150,17 @@ Base.inv(r::RotMatrix) = RotMatrix(r.mat') end """ - struct Angle2d{T} <: Rotation{2,T} - theta::T + struct Angle2d{T,A} <: Rotation{2,T} + theta::A end A 2×2 rotation matrix parameterized by a 2D rotation by angle. Only the angle is stored inside the `Angle2d` type, values of `getindex` etc. are computed on the fly. """ -struct Angle2d{T} <: Rotation{2,T} - theta::T - Angle2d{T}(theta) where T = new{T}(theta) +struct Angle2d{T,A} <: Rotation{2,T} + theta::A + Angle2d{T}(theta::A) where {T,A} = new{T,A}(theta) end @inline function Angle2d(theta) diff --git a/src/euler_types.jl b/src/euler_types.jl index 4f6f4a4b..e9f0221a 100644 --- a/src/euler_types.jl +++ b/src/euler_types.jl @@ -12,10 +12,12 @@ for axis in [:X, :Y, :Z] RotType = Symbol("Rot" * string(axis)) @eval begin - struct $RotType{T} <: Rotation{3,T} - theta::T - $RotType{T}(theta) where {T} = new{T}(theta) - $RotType{T}(r::$RotType) where {T} = new{T}(r.theta) + struct $RotType{T,A} <: Rotation{3,T} + theta::A + $RotType{T,A}(theta) where{T,A} = new{T,A}(theta) + $RotType{T}(theta::A) where {T,A} = new{T,A}(theta) + $RotType{T,A}(r::$RotType) where{T,A} = new{T,A}(r.theta) + $RotType{T}(r::$RotType) where {T} = $RotType{T}(r.theta) end @inline function $RotType(theta) @@ -220,11 +222,13 @@ for axis1 in [:X, :Y, :Z] InvRotType = Symbol("Rot" * string(axis2) * string(axis1)) @eval begin - struct $RotType{T} <: Rotation{3,T} - theta1::T - theta2::T - $RotType{T}(theta1, theta2) where {T} = new{T}(theta1, theta2) - $RotType{T}(r::$RotType) where {T} = new{T}(r.theta1, r.theta2) + struct $RotType{T,A} <: Rotation{3,T} + theta1::A + theta2::A + $RotType{T,A}(theta1,theta2) where{T,A} = new{T,A}(theta1, theta2) + $RotType{T}(theta1::A, theta2::A) where {T,A} = new{T,A}(theta1, theta2) + $RotType{T,A}(r::$RotType) where{T,A} = new{T,A}(r.theta1, r.theta2) + $RotType{T}(r::$RotType) where {T} = $RotType{T}(r.theta1, r.theta2) end @inline function $RotType(theta1, theta2) @@ -513,12 +517,14 @@ for axis1 in [:X, :Y, :Z] Rot0Type = Symbol("Rot" * string(axis0)) @eval begin - struct $RotType{T} <: Rotation{3,T} - theta1::T - theta2::T - theta3::T - $RotType{T}(theta1, theta2, theta3) where {T} = new{T}(theta1, theta2, theta3) - $RotType{T}(r::$RotType) where {T} = new{T}(r.theta1, r.theta2, r.theta3) + struct $RotType{T,A} <: Rotation{3,T} + theta1::A + theta2::A + theta3::A + $RotType{T,A}(theta1,theta2,theta3) where{T,A} = new{T,A}(theta1, theta2, theta3) + $RotType{T}(theta1::A, theta2::A, theta3::A) where {T,A} = new{T,A}(theta1, theta2, theta3) + $RotType{T,A}(r::$RotType) where{T,A} = new{T,A}(r.theta1, r.theta2, r.theta3) + $RotType{T}(r::$RotType) where {T} = $RotType{T}(r.theta1, r.theta2, r.theta3) end @inline function $RotType(theta1, theta2, theta3) From 9a33f9cd94d8be2092d1df7a24ad5206e01fafce Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Fri, 12 Nov 2021 15:19:43 +0100 Subject: [PATCH 2/8] Added a test --- test/2d.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/2d.jl b/test/2d.jl index df004cef..bc0f273e 100644 --- a/test/2d.jl +++ b/test/2d.jl @@ -18,6 +18,9 @@ using Unitful # don't extraneously contain those units (see issue #55) @test eltype(Angle2d(10u"°")) <: Real @test eltype(Angle2d(20u"rad")) <: Real + + # Check exactitude of degree rotations + @test Angle2d(360u"°") == [1 0;0 1] end ############################### From 34934763166df6c7b601adea0b6365743e4e75d4 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Fri, 12 Nov 2021 15:26:43 +0100 Subject: [PATCH 3/8] fix one test for 2-parameter RotZYX --- test/rotation_tests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rotation_tests.jl b/test/rotation_tests.jl index b1f993a3..2ec05e52 100644 --- a/test/rotation_tests.jl +++ b/test/rotation_tests.jl @@ -470,7 +470,7 @@ all_types = (RotMatrix{3}, AngleAxis, RotationVec, rxyz = RotXYZ(1.0, 2.0, 3.0) show(io, MIME("text/plain"), rxyz) str = String(take!(io)) - @test startswith(str, "3×3 RotXYZ{Float64}") && occursin("(1.0, 2.0, 3.0):", str) + @test startswith(str, "3×3 RotXYZ{Float64,") && occursin("(1.0, 2.0, 3.0):", str) end ######################################################################### From adb5941030f899a0f4e94ab3142e0b0b688e5aad Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Fri, 12 Nov 2021 15:55:25 +0100 Subject: [PATCH 4/8] all tests pass now --- src/core_types.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core_types.jl b/src/core_types.jl index 3b866ad1..4fa5975a 100644 --- a/src/core_types.jl +++ b/src/core_types.jl @@ -160,6 +160,7 @@ of `getindex` etc. are computed on the fly. """ struct Angle2d{T,A} <: Rotation{2,T} theta::A + Angle2d{T,A}(theta::Number) where{T,A} = new{T,A}(theta) Angle2d{T}(theta::A) where {T,A} = new{T,A}(theta) end @@ -171,6 +172,7 @@ params(r::Angle2d) = SVector{1}(r.theta) Angle2d(r::Rotation{2}) = Angle2d(rotation_angle(r)) Angle2d{T}(r::Rotation{2}) where {T} = Angle2d{T}(rotation_angle(r)) +Angle2d{T,A}(r::Rotation{2}) where{T,A} = Angle2d{T,A}(rotation_angle(r)) Base.one(::Type{A}) where {A<: Angle2d} = A(0) From b8d31589d914d76f7ac67a490513d04ae442e580 Mon Sep 17 00:00:00 2001 From: Circonflexe Date: Sat, 13 Nov 2021 13:11:53 +0100 Subject: [PATCH 5/8] Update src/core_types.jl Co-authored-by: Yuto Horikawa --- src/core_types.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core_types.jl b/src/core_types.jl index 4fa5975a..9f17be77 100644 --- a/src/core_types.jl +++ b/src/core_types.jl @@ -160,7 +160,7 @@ of `getindex` etc. are computed on the fly. """ struct Angle2d{T,A} <: Rotation{2,T} theta::A - Angle2d{T,A}(theta::Number) where{T,A} = new{T,A}(theta) + Angle2d{T,A}(theta::Number) where{T,A} = new{T,A}(theta) Angle2d{T}(theta::A) where {T,A} = new{T,A}(theta) end From 2c9449bc86bd2b5d657e965cc8a9922b4520d221 Mon Sep 17 00:00:00 2001 From: Circonflexe Date: Sat, 13 Nov 2021 13:12:02 +0100 Subject: [PATCH 6/8] Update test/2d.jl Co-authored-by: Yuto Horikawa --- test/2d.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/2d.jl b/test/2d.jl index bc0f273e..4b4c9f23 100644 --- a/test/2d.jl +++ b/test/2d.jl @@ -19,8 +19,8 @@ using Unitful @test eltype(Angle2d(10u"°")) <: Real @test eltype(Angle2d(20u"rad")) <: Real - # Check exactitude of degree rotations - @test Angle2d(360u"°") == [1 0;0 1] + # Check exactitude of degree rotations + @test Angle2d(360u"°") == [1 0;0 1] end ############################### From 32e924915cec29b9ea2e2898c696794fbd6e15b3 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Sat, 13 Nov 2021 16:33:58 +0100 Subject: [PATCH 7/8] AngleAxis with exact angles --- src/angleaxis_types.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/angleaxis_types.jl b/src/angleaxis_types.jl index ac14c84d..163bd528 100644 --- a/src/angleaxis_types.jl +++ b/src/angleaxis_types.jl @@ -1,7 +1,7 @@ ################################################################################ ################################################################################ """ - struct AngleAxis{T} <: Rotation{3,T} + struct AngleAxis{T,A} <: Rotation{3,T} AngleAxis(Θ, x, y, z) A 3×3 rotation matrix parameterized by a 3D rotation by angle θ about an @@ -20,19 +20,19 @@ represent a normalized rotation axis. Operations on an `AngleAxis` with a rotati axis that does not have unit norm, created by skipping renormalization in this fashion, are not guaranteed to do anything sensible. """ -struct AngleAxis{T} <: Rotation{3,T} - theta::T +struct AngleAxis{T,A} <: Rotation{3,T} + theta::A axis_x::T axis_y::T axis_z::T - @inline function AngleAxis{T}(θ, x, y, z, normalize::Bool = true) where {T} + @inline function AngleAxis{T}(θ::A, x, y, z, normalize::Bool = true) where {T,A} if normalize # Not sure what to do with theta?? Should it become theta * norm ? norm = sqrt(x*x + y*y + z*z) - new(θ, x/norm, y/norm, z/norm) + new{T,A}(θ, x/norm, y/norm, z/norm) else - new(θ, x, y, z) + new{T,A}(θ, x, y, z) end end end @@ -42,7 +42,7 @@ params(aa::AngleAxis) = SVector{4}(aa.theta, aa.axis_x, aa.axis_y, aa.axis_z) # StaticArrays will take over *all* the constructors and put everything in a tuple... # but this isn't quite what we mean when we have 4 inputs (not 9). @inline function AngleAxis(θ::Θ, x::X, y::Y, z::Z, normalize::Bool = true) where {Θ,X,Y,Z} - AngleAxis{promote_type(promote_type(promote_type(Θ, X), Y), Z)}(θ, x, y, z, normalize) + AngleAxis{reduce(promote_type, (rot_eltype(Θ),X,Y,Z,))}(θ, x, y, z, normalize) end # These functions are enough to satisfy the entire StaticArrays interface: From 980cd7724330264d03511a6f679041f37d6e60a3 Mon Sep 17 00:00:00 2001 From: Jerome Plut Date: Sun, 14 Nov 2021 08:39:26 +0100 Subject: [PATCH 8/8] tests for RotX etc. --- src/angleaxis_types.jl | 14 +++++++------- test/rotation_tests.jl | 12 ++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/angleaxis_types.jl b/src/angleaxis_types.jl index 163bd528..ac14c84d 100644 --- a/src/angleaxis_types.jl +++ b/src/angleaxis_types.jl @@ -1,7 +1,7 @@ ################################################################################ ################################################################################ """ - struct AngleAxis{T,A} <: Rotation{3,T} + struct AngleAxis{T} <: Rotation{3,T} AngleAxis(Θ, x, y, z) A 3×3 rotation matrix parameterized by a 3D rotation by angle θ about an @@ -20,19 +20,19 @@ represent a normalized rotation axis. Operations on an `AngleAxis` with a rotati axis that does not have unit norm, created by skipping renormalization in this fashion, are not guaranteed to do anything sensible. """ -struct AngleAxis{T,A} <: Rotation{3,T} - theta::A +struct AngleAxis{T} <: Rotation{3,T} + theta::T axis_x::T axis_y::T axis_z::T - @inline function AngleAxis{T}(θ::A, x, y, z, normalize::Bool = true) where {T,A} + @inline function AngleAxis{T}(θ, x, y, z, normalize::Bool = true) where {T} if normalize # Not sure what to do with theta?? Should it become theta * norm ? norm = sqrt(x*x + y*y + z*z) - new{T,A}(θ, x/norm, y/norm, z/norm) + new(θ, x/norm, y/norm, z/norm) else - new{T,A}(θ, x, y, z) + new(θ, x, y, z) end end end @@ -42,7 +42,7 @@ params(aa::AngleAxis) = SVector{4}(aa.theta, aa.axis_x, aa.axis_y, aa.axis_z) # StaticArrays will take over *all* the constructors and put everything in a tuple... # but this isn't quite what we mean when we have 4 inputs (not 9). @inline function AngleAxis(θ::Θ, x::X, y::Y, z::Z, normalize::Bool = true) where {Θ,X,Y,Z} - AngleAxis{reduce(promote_type, (rot_eltype(Θ),X,Y,Z,))}(θ, x, y, z, normalize) + AngleAxis{promote_type(promote_type(promote_type(Θ, X), Y), Z)}(θ, x, y, z, normalize) end # These functions are enough to satisfy the entire StaticArrays interface: diff --git a/test/rotation_tests.jl b/test/rotation_tests.jl index 2ec05e52..b62394e7 100644 --- a/test/rotation_tests.jl +++ b/test/rotation_tests.jl @@ -491,4 +491,16 @@ all_types = (RotMatrix{3}, AngleAxis, RotationVec, @test Rotations.params(MRP(p1,p2,p3)) == [p1,p2,p3] @test Rotations.params(RodriguesParam(p1,p2,p3)) == [p1,p2,p3] end + + @testset "Exact rotations in degrees" begin + using Unitful: ° + hascoeffs(m, l...) = all(l) do ((i,j),y) m[i,j] == y end + @test hascoeffs(RotX(30°), (1,1)=>1, (2,3)=>-1/2, (3,2)=>1/2) + @test hascoeffs(RotX(60°), (1,1)=>1, (2,2)=>1/2, (3,3)=>1/2) + @test hascoeffs(RotXY(90°,60°), (1,1)=>1/2,(2,3)=>-1/2,(3,2)=>1) + @test hascoeffs(RotXYZ(30°,60°,90°), + (1,2)=>-1/2,(2,3)=>-1/4,(3,1)=>1/2) + # FIXME: this should also have (3,2)=>3/4, but exactness here is as + # a *product* of trigonometric functions simplifying √3... + end end