İçeriğe geç

06 · Rotation: matrix, Euler açıları, axis-angle, quaternion

Bölüm2 · Spatial math
Çalışma türüCPU · Mac ya da Colab CPU
Önkoşul01
Önce öğrenLinear Algebra Intuition · Matrix Transformations
Süre~60 dk
Kodexperiments/06-rotations/experiment.py
Durum✅ tam

Bu derste yap

Her adımı yapınca işaretle. Yalnız bu tarayıcıda saklanır.

Orientation’ı rotation matrix ya da birim quaternion olarak sakla; Euler açılarını yalnızca göstermek için kullan.

Serbest hareket eden bir cisim içeren herhangi bir MuJoCo modelini açıp state vektörüne bakın. Tek bir serbest cismin nq = 7 konum sayısı (3’ü konum, 4’ü quaternion) ama yalnızca nv = 6 hız sayısı (3 doğrusal, 3 açısal) vardır; bizim çalıştırmamız tam olarak bunu ve varsayılan quaternion’ı [1, 0, 0, 0] yazdırıyor. Bir simülatör üç boyutlu bir büyüklük için neden dört sayı saklar, konum ile hız neden farklı boyuttadır?

Çünkü orientation’ın üç sayılık her tarifi bir yerde bozulur. Euler açılarında 90° pitch’e gelmiş bir gripper bir dönme yönünü kaybeder: hiçbir sonlu Euler açısı hızı o yönü üretemez. Bu ders bunun nerede olduğunu ve neye mal olduğunu ölçüyor, bozulmayan gösterimleri de gösteriyor.

Okumadan önce · 2 soru. Önce tahmin et; nedenini ders anlatıyor.

1. MuJoCo’da tek bir serbest body’nin nq = 7 konum sayısı ama yalnızca nv = 6 hız sayısı var. Neden?

  • Yedinci konum sayısı simülasyon zamanını saklar.
  • Orientation 4 sayılık bir birim quaternion olarak saklanır, açısal hız ise düz bir 3-vektördür.
  • Quaternion’ın dördüncü sayısı body’nin ölçeğini saklar.

Neden: Konum 3 + quaternion 4 = 7, doğrusal 3 + açısal 3 = 6 (çıktının A bölümü, default quaternion [1, 0, 0, 0]).

2. Gimbal lock pitch ±90°‘de olur. Euler açısı hızlarını komut eden bir controller için 80° pitch güvenli mi?

  • Evet; hız haritası yalnızca tam 90°‘de singular, öncesi sorunsuz.
  • Evet, body’nin kendisi her yöne dönebildiği sürece.
  • Hayır; det⁡E=cos⁡θ\det E = \cos\theta, yani sorun bütün bir komşulukta büyür, cond(E) 80°‘de zaten 11,43.

Neden: Adım 2 tablosu; 1 rad/s’lik gövde rotation’ı için gereken Euler hızı 85°‘de 16,2 rad/s, 89°‘de 81,0 rad/s (Boz).

Orientation “bu şey hangi yöne bakıyor?” sorusunun cevabıdır. Üç düğmeli bir tripod kafasına takılı bir kamera düşün: pan (sağa/sola çevir), tilt (yukarı/aşağı bak) ve roll (görüntüyü döndür). Üç düğmenin okuması Euler açılarıdır; insanlar kolay okur, her arayüz bu yüzden bunları gösterir.

Şimdi kamerayı tam yukarı çevir. Pan ve roll artık kamerayı aynı dikey çizgi etrafında döndürür ve bu pozdan onu yana çeviren bir düğme kalmaz: bir yön kayboldu, gimbal lock. Kamera hâlâ her yöne bakabilir; bozulan üç düğmeli tariftir ve o pozun bütün bir komşuluğunda sorun çıkarır.

Çözüm, orientation’ı bir dönüş dizisi olmadan tarif etmektir. Rotation matrix, cismin üç ekseninin nereyi gösterdiğini üst frame’in koordinatlarında yazar. Birim quaternion “bu eksen etrafında bu açı kadar dön” bilgisini dört sayıya sığdırır. İkisinin de kötü bir pozu yoktur; quaternion’ın tek tuhaflığı qq ile −q-q‘nun aynı orientation olmasıdır, quaternion karşılaştıran ya da karıştıran kod işareti kontrol etmelidir.

flowchart TD
    E["Euler açıları<br/>yaw, pitch, roll<br/>(insana göstermek için)"] -- "mju_euler2Quat" --> Q["birim quaternion q<br/>w, x, y, z<br/>(sakla, interpolasyon)"]
    A["axis-angle<br/>eksen k, açı θ"] -- "mju_axisAngle2Quat" --> Q
    Q -- "mju_quat2Mat" --> R["rotation matrix R, 3×3<br/>(bileşke, vektör döndürme)"]
    R -- "mju_mat2Quat" --> Q
    R --> P["döndürülmüş nokta<br/>p' = R p"]
import numpy as np
def Rz(a): # z ekseni etrafında a açısı (rad) kadar rotation
c, s = np.cos(a), np.sin(a)
return np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
# Ry(a) ve Rx(a), y ve x etrafında aynı şekilde kurulur
p = np.array([1.0, 0, 0])
Rz(np.pi / 2) @ p # active: vektörü döndür -> [0, 1, 0]
Rz(np.pi / 2).T @ p # passive: aynı vektör, +90° dönmüş frame'deki koordinatları -> [0, -1, 0]
R = Rz(yaw) @ Ry(pitch) @ Rx(roll) # Euler z-y'-x'': bileşke = çarpım, sıra önemli
q = np.r_[np.cos(angle / 2), np.sin(angle / 2) * axis] # birim quaternion, skaler önde (Sıfırdan yaz: slerp de)

İki Rz satırı aynı matrisin iki okunuşudur ve çalıştırma tam bu vektörleri yazdırıyor:

OkunuşFormülAnlamı
Activep′=R pp' = R\,ppp vektörünü döndür
PassivepB=RAB⊤ pAp_B = R_{AB}^\top\, p_Aaynı nokta, RABR_{AB} kadar dönmüş bir BB frame’indeki koordinatları
GösterimFormülKötü pozlar
Rotation matrixR⊤R=I, det⁡R=+1R^\top R = I,\ \det R = +1yok
Euler açıları z-y’-x”R=Rz(ψ)Ry(θ)Rx(ϕ)R = R_z(\psi)R_y(\theta)R_x(\phi)pitch θ=±90°\theta = \pm 90° (gimbal lock)
Axis-angle (Rodrigues)R=I+sin⁡θ K+(1−cos⁡θ) K2R = I + \sin\theta\,K + (1-\cos\theta)\,K^2–
Birim quaternionq=(cos⁡θ2, sin⁡θ2 k^)q = (\cos\tfrac{\theta}{2},\ \sin\tfrac{\theta}{2}\,\hat k)yok, ama qq ile −q-q aynı rotation’dır

RR, sütunları dönmüş frame’in eksenlerinin üst frame’deki ifadesi olan 3×3’lük matristir; ψ,θ,ϕ\psi, \theta, \phi yaw, pitch ve roll’dur; axis-angle satırlarında k^\hat k birim dönme ekseni, θ\theta açıdır; K=[k^]×K = [\hat k]_\times çapraz çarpımı hesaplayan matristir, Kv=k^×vKv = \hat k \times v; qq skaler önde yazılır.

Bileşke, matris çarpımıdır ve sıra değiştirilemez: Rz(90°)Rx(90°)R_z(90°)R_x(90°) ile Rx(90°)Rz(90°)R_x(90°)R_z(90°) arasında 120°‘lik bir rotation fark vardır (aşağıda ölçüldü). RAC=RABRBCR_{AC} = R_{AB} R_{BC}‘de, soldan sağa okununca, her yeni rotation yeni ulaşılan frame’in eksenleri etrafındadır (intrinsic). İki orientation’ı karıştırmak için Euler açılarında düz bir çizgi değil, en kısa yayı izleyen slerp (spherical linear interpolation) kullan.

Euler açılarının neden bozulduğu tek satırda: Euler açısı hızlarını cismin açısal hızına çeviren EE matrisinin det⁡E=cos⁡θ\det E = \cos\theta‘dır ve pitch ±90°‘de sıfırdır.

Matematik ayrıntısı: Euler hız haritası, Rodrigues ve slerp

Euler hız haritası. Intrinsic z-y’-x” için (yaw ψ\psi, pitch θ\theta, roll ϕ\phi) gövde açısal hızı, açı hızlarına doğrusal bağlıdır:

ωb=E(θ,ϕ)[ϕ˙θ˙ψ˙],E=[10−sin⁡θ0cos⁡ϕsin⁡ϕcos⁡θ0−sin⁡ϕcos⁡ϕcos⁡θ],det⁡E=cos⁡θ.\omega_b = E(\theta,\phi)\begin{bmatrix}\dot\phi\\ \dot\theta\\ \dot\psi\end{bmatrix},\qquad E=\begin{bmatrix}1&0&-\sin\theta\\0&\cos\phi&\sin\phi\cos\theta\\0&-\sin\phi&\cos\phi\cos\theta\end{bmatrix},\qquad \det E=\cos\theta .

θ=±90°\theta = \pm 90° olduğunda birinci ve üçüncü sütun paralel olur: roll ve yaw cismi aynı eksen etrafında döndürür. Bu gimbal lock’tur; fiziksel rotation’ın değil, parametrelemenin singularity’sidir. Yakınında EE hâlâ tersinirdir ama neredeyse singular’dır; sıradan bir açısal hız üretmek için çok büyük açı hızları gerekir (Boz).

Rodrigues formülü. Her rotation, birim bir k^\hat k ekseni etrafında bir θ\theta açısıdır. K=[k^]×K = [\hat k]_\times (çapraz çarpım matrisi, skew-symmetric) ile R=I+sin⁡θ K+(1−cos⁡θ) K2R = I + \sin\theta\,K + (1-\cos\theta)\,K^2.

Birim quaternion. q=(cos⁡θ2, sin⁡θ2 k^)q = (\cos\tfrac{\theta}{2},\ \sin\tfrac{\theta}{2}\,\hat k). Yarım açı yüzünden θ+2π\theta + 2\pi kadar dönmek aynı fiziksel rotation için −q-q verir: double cover, qq ile −q-q tek bir rotation’dır.

Slerp en kısa büyük çember yayı boyunca interpolasyon yapar: slerp(q0,q1,t)=sin⁡((1−t)Ω)sin⁡Ωq0+sin⁡(tΩ)sin⁡Ωq1\mathrm{slerp}(q_0,q_1,t) = \frac{\sin((1-t)\Omega)}{\sin\Omega}q_0 + \frac{\sin(t\Omega)}{\sin\Omega}q_1, cos⁡Ω=q0⋅q1\cos\Omega = q_0\cdot q_1; q0⋅q1<0q_0\cdot q_1 < 0 ise önce q1→−q1q_1 \to -q_1. Bu çevirme olmadan yay uzun yoldan dolaşabilir.

Daha derine: AIEFS Linear Algebra Intuition, Matrix Transformations; SO(3)SO(3) (bütün rotation matrix’lerin grubu) ve exponential coordinate’ların titiz anlatımı Lynch & Park, Modern Robotics, Böl. 3’te.

MuJoCo quaternion’ı skaler önde yazar; sıfır rotation 1 0 0 0’dır (MuJoCo modeling: frame orientations). Gösterimler arasında mju_euler2Quat (dizi string’i; küçük harf = intrinsic, büyük harf = extrinsic), mju_axisAngle2Quat, mju_quat2Mat, mju_mat2Quat, mju_mulQuat, mju_subQuat ile dönüşüm yapar (API reference). Baştaki bilmecenin cevabı da bu: orientation 4 quaternion sayısı, açısal hız bir 3-vektör olarak saklanır; bu yüzden nq ile nv farklıdır.

Şimdi sonraki her dersin kullandığı rotation araç kutusunu, quaternion cebirini, dönüşümleri, Euler açılarını ve slerp’i yaklaşık 80 satır NumPy ile yazacak ve her fonksiyonu MuJoCo’nun mju_* rutinleri ve SciPy ile karşılaştıracaksın. Adımları kendi dosyana yaz ve her adımdan sonra çalıştır. Bitmiş fonksiyonlar physai/spatial.py içinde, ders 09’un da kullandığı quat_to_mat ve axis_angle_to_mat’in (Rodrigues formülü) yanında; uv run python experiments/06-rotations/build.py beş adımı kontrolleriyle birlikte koşturur (çıktı build_output.txt dosyasında).

Birim quaternion q=(w,x,y,z)q = (w, x, y, z) Hamilton çarpımıyla birleşir, vektör kısmını ters çevirerek tersini alır ve bir vektörü iki yanından sararak döndürür:

def quat_mul(a, b):
"""Hamilton product a * b: the rotation b followed by a (as matrices, R(a) @ R(b))."""
w1, x1, y1, z1 = a
w2, x2, y2, z2 = b
return np.array([w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2,
w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2,
w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2,
w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2])
def quat_conj(q):
"""Conjugate (w, -x, -y, -z): the inverse rotation of a unit quaternion."""
return np.array([q[0], -q[1], -q[2], -q[3]])
def quat_rotate(q, v):
"""Rotate the 3-vector v by the unit quaternion q: the vector part of q * (0, v) * conj(q)."""
return quat_mul(quat_mul(q, np.r_[0.0, v]), quat_conj(q))[1:]

Bir quaternion’ın matrisi, dosyada zaten olan quat_to_mat:

def quat_to_mat(q):
"""Unit quaternion (w, x, y, z), MuJoCo's order, to a 3x3 rotation matrix."""
w, x, y, z = q
return np.array([
[1 - 2 * (y * y + z * z), 2 * (x * y - w * z), 2 * (x * z + w * y)],
[2 * (x * y + w * z), 1 - 2 * (x * x + z * z), 2 * (y * z - w * x)],
[2 * (x * z - w * y), 2 * (y * z + w * x), 1 - 2 * (x * x + y * y)],
])
== Step 1. Quaternion algebra vs MuJoCo (1000 random rotations) ==
quat_mul vs mju_mulQuat 2.2e-16
quat_conj vs mju_negQuat 0.0e+00
quat_rotate vs mju_rotVecQuat 1.3e-15
R(a * b) vs R(a) @ R(b) 1.2e-15
R(-q) vs R(q) 0.0e+00

Çarpım sırası matris sırasıyla aynı, R(a b)=R(a) R(b)R(a\,b) = R(a)\,R(b), ve −q-q tam olarak aynı matrisi veriyor (double cover).

Axis-angle’dan quaternion’a geçiş yarım açı formülüdür. Matristen quaternion’a geçiş dikkat ister: w=121+tr⁡Rw = \tfrac12\sqrt{1 + \operatorname{tr} R}‘yi okumak w→0w \to 0 olan 180° civarında bozulur. Shepperd yöntemi w,x,y,zw, x, y, z‘den hangisi en büyükse onu seçer ve ona böler:

def axis_angle_to_quat(axis, angle):
"""Unit quaternion for a rotation by `angle` (rad) about the unit vector `axis`."""
return np.r_[np.cos(angle / 2), np.sin(angle / 2) * np.asarray(axis, float)]
def mat_to_quat(R):
"""Rotation matrix to unit quaternion with w >= 0 (Shepperd: divide by the largest of 4w^2, 4x^2, 4y^2, 4z^2)."""
t = np.trace(R)
cands = [t, R[0, 0] - R[1, 1] - R[2, 2], R[1, 1] - R[0, 0] - R[2, 2], R[2, 2] - R[0, 0] - R[1, 1]]
i = int(np.argmax(cands))
s = 2 * np.sqrt(1 + cands[i]) # 4 times the largest component
if i == 0:
q = [s / 4, (R[2, 1] - R[1, 2]) / s, (R[0, 2] - R[2, 0]) / s, (R[1, 0] - R[0, 1]) / s]
elif i == 1:
q = [(R[2, 1] - R[1, 2]) / s, s / 4, (R[0, 1] + R[1, 0]) / s, (R[0, 2] + R[2, 0]) / s]
elif i == 2:
q = [(R[0, 2] - R[2, 0]) / s, (R[0, 1] + R[1, 0]) / s, s / 4, (R[1, 2] + R[2, 1]) / s]
else:
q = [(R[1, 0] - R[0, 1]) / s, (R[0, 2] + R[2, 0]) / s, (R[1, 2] + R[2, 1]) / s, s / 4]
q = np.array(q)
return q if q[0] >= 0 else -q
== Step 2. Axis-angle and matrix -> quaternion vs MuJoCo and SciPy ==
axis_angle_to_quat vs mju_axisAngle2Quat 1.1e-16
mat_to_quat vs mju_mat2Quat (w >= 0) 5.6e-16
mat_to_quat vs SciPy as_quat(scalar_first) 2.2e-16
quat_to_mat(mat_to_quat(R)) round trip 7.8e-16
180 deg about x: mat_to_quat gives [0. 1. 0. 0.] (trace -1: the x branch)

MuJoCo ve SciPy −q-q döndürebilir; w≥0w \ge 0 ile karşılaştırmak bu işareti ortadan kaldırır.

def euler_zyx_to_mat(yaw, pitch, roll):
"""Intrinsic z-y'-x'' Euler angles (rad) to a rotation matrix: Rz(yaw) @ Ry(pitch) @ Rx(roll)."""
cy, sy, cp, sp, cr, sr = np.cos(yaw), np.sin(yaw), np.cos(pitch), np.sin(pitch), np.cos(roll), np.sin(roll)
return np.array([[cy * cp, cy * sp * sr - sy * cr, cy * sp * cr + sy * sr],
[sy * cp, sy * sp * sr + cy * cr, sy * sp * cr - cy * sr],
[-sp, cp * sr, cp * cr]])
def mat_to_euler_zyx(R):
"""Rotation matrix to (yaw, pitch, roll), pitch in [-pi/2, pi/2]; at pitch = +-90 deg yaw and roll are not unique."""
pitch = np.arcsin(np.clip(-R[2, 0], -1, 1))
return np.arctan2(R[1, 0], R[0, 0]), pitch, np.arctan2(R[2, 1], R[2, 2])
== Step 3. Euler z-y'-x'' vs mju_euler2Quat('zyx') and SciPy from_euler('ZYX') ==
vs MuJoCo 'zyx' (lowercase = intrinsic there) 8.9e-16
vs SciPy 'ZYX' (uppercase = intrinsic there) 5.6e-16
vs SciPy 'zyx' (extrinsic: a different rotation) 2.00
mat_to_euler_zyx round trip, angles 9.9e-14 rad
pitch 89.0 deg: (yaw, roll) = (0.7, 0.3) and (0.9, 0.5) give matrices 3.2e-03 apart
pitch 90.0 deg: (yaw, roll) = (0.7, 0.3) and (0.9, 0.5) give matrices 1.1e-16 apart

Aynı üç açı MuJoCo’nun küçük harfli "zyx"’inde de SciPy’ın büyük harfli "ZYX"’inde de RzRyRxR_z R_y R_x demek: iki kütüphane intrinsic diziler için zıt harf büyüklüğü kullanıyor ve SciPy’ın küçük harfi farklı bir rotation veriyor. Son iki satır sayılarla gimbal lock: 90°‘de iki farklı (yaw, roll) çifti tek bir matris veriyor, yani hiçbir çıkarım onları geri bulamaz.

def euler_rate_matrix(pitch, roll):
"""E with body angular velocity w_b = E @ (roll', pitch', yaw') for z-y'-x'' angles; det E = cos(pitch)."""
cp, sp, cr, sr = np.cos(pitch), np.sin(pitch), np.cos(roll), np.sin(roll)
return np.array([[1, 0, -sp], [0, cr, sr * cp], [0, -sr, cr * cp]])

Her sütunu finite difference ile kontrol et: bir açıyı biraz oynat; gövdenin açısal hızı R⊤R˙R^\top \dot R‘nin skew kısmıdır.

== Step 4. The Euler-rate matrix E (roll = 30 deg) ==
pitch 0.00 deg: det E 1.000e+00 (cos 1.000e+00) · cond 1.00 · |E - E_fd| 5e-11
pitch 60.00 deg: det E 5.000e-01 (cos 5.000e-01) · cond 3.73 · |E - E_fd| 8e-11
pitch 80.00 deg: det E 1.736e-01 (cos 1.736e-01) · cond 11.43 · |E - E_fd| 8e-11
pitch 89.00 deg: det E 1.745e-02 (cos 1.745e-02) · cond 114.59 · |E - E_fd| 7e-11
pitch 89.90 deg: det E 1.745e-03 (cos 1.745e-03) · cond 1145.92 · |E - E_fd| 7e-11
pitch 89.99 deg: det E 1.745e-04 (cos 1.745e-04) · cond 11459.16 · |E - E_fd| 7e-11

det⁡E=cos⁡θ\det E = \cos\theta, yazılan her basamakta; condition number’lar da Deney’in Adım 2 tablosu (80°‘de 11,43, 89°‘de 114,59).

def slerp(q0, q1, t, shortest=True):
"""Spherical linear interpolation; with shortest=True flip q1 when q0 . q1 < 0 (q1 and -q1 are one rotation)."""
dot = q0 @ q1
if shortest and dot < 0:
q1, dot = -q1, -dot
om = np.arccos(np.clip(dot, -1, 1))
if om < 1e-9:
return q0.copy()
return (np.sin((1 - t) * om) * q0 + np.sin(t * om) * q1) / np.sin(om)
def quat_angle(q0, q1):
"""Angle (rad) of the rotation that takes orientation q0 to q1, in [0, pi]; q and -q give the same answer."""
return 2 * np.arccos(np.clip(abs(q0 @ q1), 0, 1))

Aralarında 60° olan 300 çifti, yarısında q1q_1‘i −q1-q_1 ile değiştirerek interpole et ve 100 küçük adımda kat edilen rotation’ı topla:

== Step 5. Slerp vs SciPy, and the sign check (300 pairs, 60 deg apart) ==
slerp(q0, q1, 0.3) vs SciPy Slerp (w >= 0): 3.3e-16 · pairs with q0.q1 < 0: 143
sign check True : rotation travelled min 60.0 deg, max 60.0 deg
sign check False: rotation travelled min 60.0 deg, max 300.0 deg

Deney’in Boz bölümü, kendi slerp’inden: işaret kontrolü 60° ile 300° arasındaki fark. (Bu çekilişte 143 negatif çift var, Deney’de 156.)

physai.spatial artık quat_mul, quat_conj, quat_rotate, axis_angle_to_quat, mat_to_quat, euler_zyx_to_mat, mat_to_euler_zyx, euler_rate_matrix, quat_angle ve slerp içeriyor. Ders 07 rigid transform’ları bunların üzerine kuruyor; ders 09 gövde orientation’larını quat_to_mat ile okuyor. tests/test_06_rotations.py bunları MuJoCo ve SciPy’a sabitliyor; bir şey değiştirdikten sonra uv run pytest tests/test_06_rotations.py çalıştır.

MuJoCo’nun mju_* fonksiyonları sonucu önceden ayrılmış bir çıktı dizisine, ilk argümana yazar:

q = np.zeros(4)
mujoco.mju_euler2Quat(q, np.array([yaw, pitch, roll]), "zyx") # intrinsic z-y'-x''
R = np.zeros(9); mujoco.mju_quat2Mat(R, q) # row-major 3x3, flattened
mujoco.mju_mulQuat(q_ab, q_a, q_b) # q_a * q_b
mujoco.mju_rotVecQuat(v_out, v, q)
from scipy.spatial.transform import Rotation
r = Rotation.from_quat(q, scalar_first=True) # SciPy's default order is (x, y, z, w)
r.as_euler("ZYX") # uppercase = intrinsic in SciPy
AraçQuaternion sırasıIntrinsic z-y’-x”
physai, MuJoCo(w,x,y,z)(w, x, y, z)euler_zyx_to_mat; mju_euler2Quat(q, e, "zyx")
SciPy Rotationscalar_first=True verilmezse (x,y,z,w)(x, y, z, w)from_euler("ZYX", e); küçük harf extrinsic
MJCF euler="..."–<compiler eulerseq="zyx"/>; default "xyz" (yukarıda, MuJoCo’da)

Tuzaklar:

  • Harf büyüklüğü anlamı çevirir. Adım 3: "zyx" MuJoCo’da intrinsic, SciPy’da extrinsic ve hiçbir şey hata vermez.
  • Matrisler düzdür. mju_quat2Mat ve d.xmat 9 sayıyı row-major sırada tutar; çarpmadan önce reshape(3, 3).
  • Quaternion işaretleri. İki doğru dönüşüm qq ve −q-q döndürebilir; rotation’ları dört sayı üzerinde np.allclose ile değil quat_angle ile karşılaştır.

Yazdıktan sonra · 2 soru.

1. Sıfırdan yaz, Adım 3’te aynı açılar (yaw, pitch, roll) MuJoCo’nun "zyx"’iyle de SciPy’ın "ZYX"’iyle de RzRyRxR_z R_y R_x veriyor, SciPy’ın "zyx"’i ise 2,00’ye kadar uzak bir matris veriyor. Neden?

  • SciPy quaternion’ları skaler sonda sakladığı için matrisleri transpoze çıkar.
  • İki kütüphane intrinsic diziler için zıt harf büyüklüğü kullanıyor; SciPy’ın küçük harfi extrinsic demek, yani farklı bir rotation.
  • SciPy’ın Euler dönüşümündeki rastgele yuvarlama.

Neden: mju_euler2Quat küçük harfi, SciPy büyük harfi intrinsic sayar; ikisi de euler_zyx_to_mat ile 1e-15’in altında örtüşüyor. Karıştırınca hiçbir şey hata vermez (Kütüphaneyle kullan, Tuzaklar).

2. mat_to_quat neden her zaman w=121+tr⁡Rw = \tfrac12\sqrt{1 + \operatorname{tr} R}‘yi hesaplayıp ona bölmek yerine önce w,x,y,zw, x, y, z‘nin en büyüğünü seçiyor (Shepperd yöntemi)?

  • 180°‘lik bir rotation’a yakın w→0w \to 0 olur ve küçücük bir ww‘ye bölmek yuvarlama hatalarını büyütür; en büyük bileşen her zaman en az 1/2’dir.
  • Girdilerin yarısında qq yerine −q-q döndürmek için.
  • Çünkü bir rotation matrisinin izi 3’ten büyük olabilir.

Neden: xx etrafında 180° için iz −1 ve ww = 0; Sıfırdan yaz, Adım 2 orada xx dalını seçip [0, 1, 0, 0] döndürüyor. 1000 rastgele rotation üzerinde mju_mat2Quat ile 5,6e-16 farkla örtüşüyor.

Script: experiments/06-rotations/experiment.py. MuJoCo 3.17.0, NumPy 2.5.4, Apple Silicon Mac (Apple M5), sabit seed’ler, 10 s’nin altında.

Önce tahmin et. Euler hız haritası hangi pitch’te sorun olur: 45°, 80°, yoksa yalnızca tam 90°‘de mi? Saf Euler açısı interpolasyonu gerçek en kısa rotation’dan ne kadar sapabilir?

Adım 1 · dönüşümler tutarlı. 1000 rastgele rotation; elle yazılmış matrisler ile MuJoCo fonksiyonları.

q = np.zeros(4)
mujoco.mju_euler2Quat(q, [yaw, pitch, roll], "zyx") # intrinsic z-y'-x''
R = np.zeros(9); mujoco.mju_quat2Mat(R, q)
Kontrol (1000 rastgele rotation)en büyük mutlak hata
mju_euler2Quat("zyx") ile RzRyRxR_zR_yR_x5,6e-16
mju_axisAngle2Quat ile Rodrigues6,7e-16
mju_mat2Quat → mju_quat2Mat gidiş-dönüş4,2e-16
qq ile −q-q matrisi0,0e+00
1000 bileşke sonrası ∣R⊤R−I∣\lvert R^\top R - I\rvert1,4e-14

Adım 2 · pitch sweep’i → EE‘nin koşullanması. Condition number cond(E), EE‘yi ters çevirdiğinde göreli hataları ne kadar büyütebileceğini söyler: 1 mükemmeldir, sonsuz bir yönün kaybolduğu anlamına gelir. σ_min, yani en küçük singular value, EE‘nin en zayıf yönü ne kadar küçülttüğüdür. Analitik EE, RR‘nin merkezi farklarıyla kontrol edildi (her satırda uyum ≤ 9,6e-11). Roll 30°‘de sabit.

pitchdet Eσ_mincond(E)
0°1,001,001,000
60°0,5000,3663,732
80°0,1740,12311,43
89°0,01750,0123114,6
89,9°0,001750,001231146
89,99°0,0001750,00012311460
90°6,12e-173,40e-17sonsuz

Condition number 1/cos⁡θ1/\cos\theta gibi büyüyor, 80°‘de bile 11,43: sorun bir nokta değil, bir komşuluk.

Adım 3 · başlangıç pitch’i sweep’i → interpolasyon hatası. Her satırda 300 çift; hedef, başlangıcın rastgele bir eksen etrafında 60° döndürülmüş hâli. Saf interpolasyon (yaw, pitch, roll) üzerinde doğrusal; yaw ve roll kısa yoldan sarılıyor; referans slerp. “sapma” iki yol arasındaki en büyük açı, “yol/60” Euler yolunun gerçekte ne kadar rotation katettiği.

başlangıç pitchmedyan sapmap95 sapmaen büyük sapmamedyan yol/60°sapması > 90° olan çift
0°3,71°6,30°6,95°1,0100
30°4,44°14,88°20,62°1,0140
60°7,83°23,91°179,74°1,0427
85°14,96°178,59°179,83°1,14922
89°16,99°178,73°179,99°1,19023

Solda: Euler hız haritasının condition number'ı ve 1 rad/s'lik gövde rotation'ı için gereken Euler hızı; pitch 90 dereceye her on kat yaklaşmada ikisi de on kat artıyor. Sağda: saf Euler interpolasyonunun slerp'ten sapmasının başlangıç pitch'ine göre değişimi; en büyük sapma 60 derece pitch'ten itibaren, 95. yüzdelik 85 dereceden itibaren yaklaşık 180 dereceye sıçrıyor.

Pitch 0’da bile Euler yolu en kısa yol değil (medyan 3,71° sapma). 90° yakınında hedefin pitch’i ±90° sınırını geçebiliyor; çıkarılan yaw ve roll yaklaşık 180° sıçrıyor ve interpolasyonla bulunan orientation uzun yoldan dolaşıyor: katetmesi gereken 60°‘nin 5,595 katına kadar.

Euler hızları patlar. EE‘nin 90°‘de kaybettiği yön etrafında (roll 30° için ωb∝(0,sin⁡ϕ,cos⁡ϕ)\omega_b \propto (0, \sin\phi, \cos\phi)) 1 rad/s’lik gövde rotation’ı iste ve E α˙=ωbE\,\dot{\boldsymbol\alpha} = \omega_b‘yi çöz:

pitch0°60°85°89°89,9°89,99°
gereken ‖Euler hızı‖ (rad/s)1,02,616,281,0810,38102,8

Euler açısı hızı komut eden bir controller, tamamen sıradan bir hareket için actuator’larını doyuma sokar (hız ya da tork sınırına dayandırır). Aynı mekanizmayı ders 10’da kinematik singularity olarak göreceksin: hızları hızlara eşleyen bir matris rank kaybediyor.

İşaret kontrolü olmadan slerp. Aynı şekilde 60° aralıklı 300 çift, ama q1q_1 rastgele olarak −q1-q_1 ile değiştiriliyor (aynı rotation). 300 çiftin 156’sında q0⋅q1<0q_0\cdot q_1 < 0 çıktı. Kontrolle her yol 60,0° katediyor; kontrolsüz en kötü yol 300,0° katediyor: uzun yoldan, çünkü quaternion yayı 360°−60°360° - 60°‘lik rotation’ı kapsıyor.

  • SO-101’in joint’leri hinge’dir (her biri sabit tek bir eksen etrafında döner); joint açıları hiçbir zaman gimbal lock yaşamaz. Sorun bir üst seviyede çıkar: gripper orientation hedefini roll-pitch-yaw olarak yazdığın her yerde (teleoperation arayüzleri, dataset etiketleri, policy action’ları). Quaternion sakla ve quaternion ile interpolasyon yap; Euler açısına yalnızca bir insana göstermek için çevir.
  • Araçlar arasında konvansiyonlar farklıdır. MuJoCo skaleri öne koyar; eulerseq compiler varsayılanı "xyz"’dir ve dokümantasyon URDF’nin “rpy”sinin MJCF’te "XYZ"’ye (extrinsic) karşılık geldiğini söyler (XML reference: compiler/eulerseq). Birçok başka kütüphane skaleri sona koyar. Kaydedilmiş bir orientation’a güvenmeden önce bilinen bir rotation’ı çevirip kontrol et.
  • IMU’lar ve kamera tracker’ları gürültülü orientation tahmini verir. Bu dersteki hiçbir şey donanımda ölçülmedi; yukarıdaki sayılar double precision’da saf matematiktir.

Hızlı kontrol · 2 soru. Bir cevaba tıkla.

1. Deney Adım 3’te, başlangıç pitch’i 85° iken naif Euler açısı interpolasyonu slerp’ten 179,83°‘ye kadar sapıyor. Böyle bir sıçramaya ne yol açıyor?

  • Slerp’in kendisi 90° civarında kararsız.
  • Hedefin pitch’i ±90° sınırını geçiyor, çıkarılan yaw ve roll yaklaşık 180° sıçrıyor ve açılardaki düz çizgi uzun yoldan dolanıyor.
  • Floating-point yuvarlaması 1/cos⁡θ1/\cos\theta gibi büyüyor.

Neden: Orientation’ın kendisi sorunsuz; sıçrayan üç açılı tarif. Euler yolu olması gereken 60°‘nin 5,595 katına kadar dolaşabiliyor, slerp ise her zaman 60,0° gidiyor.

2. İşaret kontrolü kaldırılınca, 60° aralıklı iki orientation arasındaki slerp en kötü durumda 300,0° yol alıyor. Neden?

  • qq ile −q-q aynı rotation; q0⋅q1<0q_0\cdot q_1 < 0 iken q1q_1 çevrilmezse yay uzun yoldan gider, 360° − 60°.
  • Slerp 45°‘nin üstündeki açılarda hatalıdır, daha küçük adımlar ister.
  • Quaternion’lar karıştırılmadan önce normalize edilmemişti.

Neden: Boz bölümünde 300 çiftin 156’sında q0⋅q1<0q_0\cdot q_1 < 0 çıktı; kontrolle her yol 60,0° katediyor.

  1. Kolay. Rx(90°)⊤R_x(90°)^\top, x etrafında −90°‘lik active rotation mı, yoksa +90° dönmüş bir frame’e passive koordinat değişimi mi? (İkisini de cevapla.)
  2. Orta. Adım 2’yi extrinsic "XYZ" dizisi (her dönüş sabit üst eksenler etrafında) için tekrarla. Hız haritası hangi pitch’te singular olur?
  3. Zor. interp_stats’ı, saf interpolasyon hedefin Euler üçlüsünü önce başlangıca en yakın dala açacak şekilde değiştir ((ψ,θ,ϕ)(\psi,\theta,\phi) ve (ψ±π,π−θ,ϕ±π)(\psi\pm\pi, \pi-\theta, \phi\pm\pi) ikisini de dene). Başlangıç pitch’i 85°‘de en büyük sapma 90°‘nin altına iner mi? Medyan iner mi?
  1. İkisi de: active rotation olarak Rx(90°)⊤=Rx(−90°)R_x(90°)^\top = R_x(-90°)‘dir; aynı zamanda üst frame’deki koordinatları x etrafında +90° dönmüş bir frame’e taşır.
  2. Sabit eksenler etrafında önce X, sonra Y, sonra Z, R=Rz(c)Ry(b)Rx(a)R = R_z(c)R_y(b)R_x(a) verir: intrinsic z-y’-x” ile aynı matris. Bu yüzden aynı yerde, ortadaki açı ±90° iken singular olur. Extrinsic diziyi mju_euler2Quat’a verip matrisleri karşılaştırarak kontrol et.
  3. Bu dersin script’inde hesaplanmadı. Çalıştır ve Adım 3 tablosuyla karşılaştır; ne bulursan bul, pitch 0’daki medyan sapma sıfıra inemez, çünkü Euler açılarında düz bir çizgi SO(3)SO(3)‘te geodesic (en kısa yol) değildir.
  • Rotation matrix ve birim quaternion singularity içermez; Euler açılarının hız haritasında det⁡E=cos⁡θ\det E = \cos\theta, 80° pitch’te cond zaten 11,43.
  • Euler açıları arasında asla interpolasyon yapma; q0⋅q1<0q_0\cdot q_1<0 çevirmesiyle slerp gerçek 60,0°‘yi katediyor, saf Euler yolları bunun 5,595 katına kadar katetti.
  • Konvansiyonunu bil: skaler önde ya da sonda, intrinsic (küçük harf) ya da extrinsic (büyük harf), active ya da passive.

Sonraki: ders 07 · Rigid transform’lar ve frame tree’leri.

Kaynaklar