Rotations.jlで回転しよう!
回転の導入
「回転」というのは直観的なイメージ通り、物体の形状を変化させずに向きを変える変換のことです。
線形代数では、回転を直交行列で表すことができます。
ただし正確に言えば、鏡像は回転に含めたくないので行列式が
つまり
R^{-1} = R^{\top} \det(R) = 1
を満たす行列
このような行列全体は群をなすので、記号
ここでの
鏡像も含むもの、つまり
直交行列を複素数に拡張したものとして、ユニタリ行列があります。転置
R^{-1} = R^{*} \det(R) = 1
を満たす行列全体を考えて、特殊ユニタリ群
行列式の制限がないもの、つまり
私の過去の関連発表
回転については何度かLTなどで発表しています。
本記事を読む際にも以下が部分的に役立つと思います。
- 第1回日曜数学会
- 日程: 2015/06/20
- 「逆数の作図からCayley変換まで」というタイトルでLTしました。
-
とSO(3) が準同型であることを、一次分数変換を通じて確認しました。SU(2) - 発表資料
- 第4回日曜数学会
- JuliaTokai #10
次元について
回転が作用する空間の次元
-
は直線なので回転行列は単位行列しかないので自明です。n=1 -
は原点中心の角度n=2 回転ですべての回転行列を表せます。\theta -
より上では回転は非可換で、紛らわしくなってきます。n=3 -
は応用が少ないです。私達の住んでいる空間は3次元だからです。n\ge 4
そういう訳で、応用上特に重要で紛らわしいのが
2次元と3次元の回転[1]を扱うための色々な関数を用意しているJuliaパッケージ、それがRotations.jlです!
SO(n) の次元
特殊直交群の次元は一般に
つまり、局所的には
これは行列の要素数
2次元でも同様で、
数値的に回転を扱うときの注意点
簡単のために、2次元で確認しましょう。
適当に回転行列を作って行列式を計算すると以下のようになります。
julia> using LinearAlgebra
julia> a = 0.1;
julia> R = [cos(a) -sin(a);sin(a) cos(a)]
2×2 Matrix{Float64}:
0.995004 -0.0998334
0.0998334 0.995004
julia> det(R)
1.0000000000000002
行列式は1のはずなのに…厳密に1にならないですね。
これはコンピュータが浮動小数点数を使っているために起こっています。
誤差は累積していくので、べき乗を繰り返すとどんどんずれていきます。
julia> det(R)
1.0000000000000002
julia> det(R^100)
1.0000000000000133
julia> det(R^10000)
1.000000000001315
julia> det(R^1000000)
1.0000000001314495
julia> det(R^100000000)
1.000000013144949
julia> det(R^10000000000)
1.000001314495683
julia> det(R^1000000000000)
1.0001314581217502
julia> det(R^100000000000000)
1.013231722819454
このような誤差の問題を避けるためには、
パラメータ3つで回転を表す場合は、9次元空間
浮動小数点数を使うので、依然として
また、この方針は実行速度・メモリ使用量の観点からも効果的です。
パラメータが少なくなるので計算量が減り、同時に必要なメモリも削減できます。
では、どのようにしてパラメータを減らすのが適切でしょうか?
-
ではパラメータとして回転角を取れば良いので、単純ですね。SO(2) -
では少し込み入っています。色々な便利なパラメータがあり、状況に合わせて使い分ける必要があります。次節で簡単に紹介します。SO(3)
忙しい人のための3次元回転パラメータ入門
詳しい話は本記事末尾の参考文献を参照してください。
概要だけ述べます。
- 回転行列
SO(3) -
実行列。パラメータ9つ。3\times3
-
- ユニタリ行列
SU(2) -
複素行列。パラメータ4つ。2\times2 - 一次分数変換(Möbius変換)で
と2:1で対応。SO(3)
-
- 単位四元数
- 単位長さの四元数
。パラメータ4つ。q=w+ix+jy+kz -
と代数的に同型。SU(2) - 3次元球面
と幾何的に同型。S^3 - 一次分数変換と
を経由せずに、直接SU(2) と対応付ける説明もある。SO(3)
- 単位長さの四元数
- MRP (Modified Rodrigues Parameters)
-
の局所座標。パラメータ3つ。S^3 -
から南極(S^3 )を除いてw=-1 へ立体射影で対応させる。\mathbb{R}^3
-
- Rodrigues Parameters
-
の局所座標。パラメータ3つ。S^3 -
から赤道(S^3 )を除いてw=0 へ球面原点からの射影で対応させる。\mathbb{R}^3
-
- 軸回り回転
- 単位長さ回転軸と回転角。パラメータ4つ。
- 任意の3次回転行列は適当な回転軸回りの回転になるので可能。
- 軸回り回転2
- 回転軸×回転角。パラメータ3つ。
- 回転軸を表す単位長さベクトルに回転角を掛ければパラメータを減らせる!
- Lie代数
と相性が良い。\mathfrak{so}(3)
- Euler角
- 座標軸まわりの3回の回転で回転行列を表す。パラメータ3つ。
- 直感的だが、ジンバルロック・非可換性等の影響があり使いやすくはない。
Rotations.jlの方法
さて、ここからが本題です。
以下のコード例では、Rotations.jl
がインストールされていてusing Rotations
されているとします。
回転は実行列として扱われる
julia> Rotation <: AbstractMatrix
true
これまでの説明からすれば、3次元回転をtrue
は当り前のように見えます。しかし…
- 「単位四元数
こそ回転」と考える人もいます。(\simeq SU(2)) -
AbstractQuaternion
なる抽象型は存在しないので、この場合はRotation <: Number
になります。 - 単位四元数の方が都合が良いのは「単連結になっているから」等いくつか理由があります。
-
- 「回転という操作は幾何的なもので、行列に変換可能であっても行列として扱うべきでない」という人もいます。
- この場合は
Rotation <: Any
のみの階層関係です。 - scipyの回転はこれに近いですね。
- この場合は
Rotations.jlで回転を行列として扱うのは、行列としての演算(method)をそのまま使えると便利だからです。
色々なパラメータに合わせた具象型が存在
Rotations.jlでの型の階層関係は以下のようになっています。
julia> using StaticArrays, Rotations
julia> Rotation <: StaticMatrix <: AbstractMatrix
true
julia> Rotation{2,Float64} <: StaticMatrix{2,2,Float64} <: AbstractMatrix{Float64}
true
julia> Rotation{3,Float64} <: StaticMatrix{3,3,Float64} <: AbstractMatrix{Float64}
true
Rotation{3}は3次元の回転を表すための抽象型で、以下のような具象型をsubtypeを持ちます。
ちょうど前述のパラメータのそれぞれに対応するようになっています。[2]
- 回転行列
SO(3) RotMatrix{3}
- ユニタリ行列
(単位四元数と同一なので存在せず)SU(2) - 単位四元数
QuatRotation
- MRP (Modified Rodrigues Parameters)
MRP
- Rodrigues Parameters
RodriguesParam
- 軸回り回転
AngleAxis
- 軸回り回転2
RotationVec
- Euler角
RotXYZ
,RotXYX
etc.
Rotation{2}は単純で、以下の2種類があります。
-
RotMatrix{2}
: 内部的に の2\times 2 SMatrix
を保持するもの -
Angle2d
: 内部的に回転角のみを保持するもの
これらの具象型は以下のようにインスタンスを生成して変換することができます。
julia> r = RotX(1.2) # x軸回りで1.2(rad)回転
3×3 RotX{Float64} with indices SOneTo(3)×SOneTo(3)(1.2):
1.0 0.0 0.0
0.0 0.362358 -0.932039
0.0 0.932039 0.362358
julia> AngleAxis(r) # 軸回りの回転に変換
3×3 AngleAxis{Float64} with indices SOneTo(3)×SOneTo(3)(1.2, 1.0, 0.0, 0.0):
1.0 0.0 0.0
0.0 0.362358 -0.932039
0.0 0.932039 0.362358
julia> QuatRotation(r) # 四元数回転に変換
3×3 QuatRotation{Float64} with indices SOneTo(3)×SOneTo(3)(Quaternion{Float64}(0.825336, 0.564642, 0.0, 0.0, true)):
1.0 0.0 0.0
0.0 0.362358 -0.932039
0.0 0.932039 0.362358
julia> Rotations.params(QuatRotation(r))
4-element SVector{4, Float64} with indices SOneTo(4):
0.8253356149096782
0.5646424733950354
0.0
0.0
剛体回転の例
Rotations.jlの応用として、剛体回転を実装してみましょう。
テニスラケットの定理を数値計算で確かめたかったのですが、実装が間に合いませんでした!
(あとで追記します)
今後のRotations.jl
Rotations.jlは少し歴史の長いパッケージで、Julia v0.4のころから開発が始まっていたようです。私がRotations.jlの開発に加わったのは今年に入ってからで、大きなところだと以下のPRを行いました。
-
rand
関数のバグの修正(#151)- 以前のバージョンではランダムな回転を正しく生成できていませんでした。
- Documenter.jlを使ったドキュメント作成(#169)
-
Quaternions.jl
との互換性の向上(#175)- 四元数を扱うためのパッケージQuaternions.jlとの変換ができるようになりました。
まだまだ開発は続いていて、将来的には以下の機能を追加する予定です。
-
RotationGenerator
の追加- 回転の生成子を扱うための抽象型(反対称行列)
-
v1.2.0
で追加予定です。(#203) - 回転行列の
log
を取ってRotationGenerator
を返すようになります。 -
exp(::RotationGenerator)
で回転行列が得られるようになります。
- ChainRulesCore.jlとの連携
-
v1.3.0
で追加予定です。 - Zygote.jlなどの自動微分パッケージを適用するために修正する予定です。
-
参考文献
-
物理のためのリー群とリー代数
-
とSO(3) の関係についての記載があります。SU(2)
-
-
3次元回転: パラメータ計算とリー代数による最適化
- 回転の推定について詳しいです。
-
Fundamentals of Spacecraft Attitude Determination and Control
- Rotations.jlで主に参照されている書籍です。宇宙船の制御のための書籍ですが、回転についての記述が充実しています。
-
Visualizing quaternions (4d numbers) with stereographic projection
- 単位四元数を立体射影で可視化する話
-
Quaternions and 3d rotation, explained interactively
- 単位四元数で3次元回転する話
Discussion