제1부 수학적 기초
1.11 회전의 표현 III: 이중 쿼터니언과 기하 대수 — 단위 이중 쿼터니언·등각 기하 대수(CGA)·DQ Robotics·gafro
1.11.1 이 장에서 배우는 것 / 선수 지식 / 특히 중요한 독자
이 장은 회전과 이동을 하나의 대수 원소로 묶는 두 표현, 곧 단위 이중 쿼터니언(unit dual quaternion)과 등각 기하 대수(conformal geometric algebra, CGA)의 모터(motor)를 다룬다. 이중 쿼터니언은 쿼터니언(1.5)의 계수를 이중수(dual number)로 넓혀 강체 변환을 8개 성분으로 표현하며, 합성이 곱이고 스크루 운동(1.10)을 쌍대각으로 직접 나타낸다. CGA는 점·구·평면·직선을 같은 대수의 원소로 두고 변환을 샌드위치 곱으로 하며, 그 모터가 이중 쿼터니언과 같은 8개 성분을 갖는다. 다룰 질문은 다섯 가지다. 이중 쿼터니언은 어떻게 구성되고 합성·점 변환은 어떻게 계산하는가, 스크루 선형 보간(ScLERP)은 무엇이며 회전·이동을 따로 보간하는 방법과 어떻게 다른가, 쿼터니언의 이중 덮개(Q와 −Q)와 제약 위반은 수치에서 어떻게 나타나는가, CGA의 모터는 이중 쿼터니언과 어떻게 대응하는가, 라이브러리(DQ Robotics·gafro)의 규약에서 무엇을 확인해야 하는가.
배우는 것:
- 이중수와 이중 쿼터니언 — $\varepsilon^2=0$, $Q=r+\varepsilon d$, 단위 조건, $(R,t)$와의 변환, 합성, 점 변환.
- 스크루 형식과 ScLERP — 쌍대각, $Q^t$, 최단 경로, 회전·이동 분리 보간과의 차이.
- 수치 — 이중 덮개의 부호 문제, 반복 합성의 제약 표류, 곱셈 횟수.
- CGA — 영벡터 점, 거리 내적, 구·평면, 모터와 샌드위치 곱, 이중 쿼터니언과의 대응.
- 라이브러리 규약 — DQ Robotics의 성분 순서와
pow의 정의, gafro의 모터 기저 순서.
선수 지식: 1.5(쿼터니언), 1.6(동차변환), 1.7(지수·로그), 1.10(스크루 이론). 표기는 BK-05와 A.2을 따른다: 쿼터니언은 Hamilton 곱·스칼라 먼저 $(q_w,q_x,q_y,q_z)$.
특히 중요한 독자: 기구학·궤적 보간을 구현하는 엔지니어, 이중 쿼터니언·기하 대수 라이브러리를 선택하는 연구자, 포즈 보간과 블렌딩을 다루는 소프트웨어 개발자.
출처 방침. 서지는 Crossref와 arXiv 응답으로 확인했다(목록은 이 장 끝). 직접 읽은 자료: ① DQ Robotics C++의
DQ.cpp(log·exp·pow)·DQ.h, ② gafro의Motor.hpp·README.md. 직접 실행한 것:dqrobotics사전 릴리스 26.4.0a7(Python 3.12, NumPy 2.5.4)과 안정 릴리스 20.4.0.79(Python 3.9, NumPy 1.26.4). 읽지 못한 것: 인용한 논문·교과서의 본문(정리 번호는(확인 필요)), gafro의 실행과 부호 규약, DQ Robotics의 문서. 이 장의 수치는 모두 순수 파이썬 구현으로 계산했으며 1.1의 행렬 곱, 1.5의 쿼터니언, 1.7의 지수·로그, 1.10의 스크루 함수를 불러와 사용했다.
1.11.2 개념 — 쿼터니언에 이동을 더하기
쿼터니언 $r$는 회전 하나를 4개 수로 나타낸다. 이동 $t$를 함께 나타내려면 쿼터니언의 계수를 이중수 $a+\varepsilon b$($\varepsilon\ne0$, $\varepsilon^2=0$)로 바꾼다. 그러면 원소는 쿼터니언 두 개 $(r,d)$, 곧 8개 성분이고 곱셈은 $\varepsilon^2=0$ 때문에 $(r_1,d_1)(r_2,d_2)=(r_1r_2,\ r_1d_2+d_1r_2)$가 된다. 회전을 먼저 하고 이동을 나중에 하는 $Q=r+\tfrac{\varepsilon}{2}\,t\,r$가 강체 변환이며 합성이 곱이다.
이 구조는 1.10의 스크루와 같다. 쿼터니언이 "축 둘레의 각도 $\theta$"로 표현되듯이 이중 쿼터니언은 "선(축) 둘레의 쌍대각 $\theta+\varepsilon h\theta$"로 표현된다. CGA는 한 걸음 더 나아가 점과 구·평면까지 같은 대수에 넣고, 변환을 $MP\tilde M$ 형태의 샌드위치로 나타낸다. 모터 $M$의 8개 성분은 이중 쿼터니언과 일대일로 대응한다.
1.11.3 원리
1.11.3.1 이중수와 단위 이중 쿼터니언
결론: 단위 이중 쿼터니언은 8개 성분과 2개 제약($\lVert r\rVert=1$, $r\cdot d=0$)으로 6자유도의 강체 변환을 나타낸다. $Q$와 $-Q$는 같은 변환이다.
이중 쿼터니언 $Q=r+\varepsilon d$($r,d$는 쿼터니언)의 켤레는 두 부분에 쿼터니언 켤레를 취한 $Q^*=r^*+\varepsilon d^*$이다. 단위 이중 쿼터니언은 $QQ^*=1$이며 이는 두 조건 $\lVert r\rVert=1$과 $r\,d^*+d\,r^*=0$($r$와 $d$의 4차원 내적이 0)과 같다. 회전 $r$와 이동 $t$(순수 쿼터니언 $(0,t)$)로부터
$$Q=r+\frac{\varepsilon}{2}\,t\,r,\qquad t=2\,d\,r^*$$
이다. 합성은 $Q_1Q_2$이며 변환의 합성 $(R_1R_2,\ R_1t_2+t_1)$에 대응한다. 부호가 반대인 $-Q$는 같은 변환을 나타내므로(이중 덮개) 변환 하나에 쌍대 쿼터니언 두 개가 대응한다.
1.11.3.2 점 변환
점 $x$를 $1+\varepsilon x$로 두고 제3 켤레 $Q^\dagger=r^*-\varepsilon d^*$로 샌드위치를 취한다.
$$Q\,(1+\varepsilon x)\,Q^\dagger=1+\varepsilon\,(Rx+t)$$
$Q=r+\tfrac\varepsilon2tr$를 대입하면 $r\,x\,r^*+\tfrac12t+\tfrac12t=Rx+t$가 되어 위 식이 성립한다. 이 장은 이를 무작위 입력에서 $10^{-15}$ 안에서 확인했다.
1.11.3.3 스크루 형식과 ScLERP
결론: 단위 이중 쿼터니언은 선 $\hat s=s+\varepsilon m$($m=q\times s$)와 쌍대각 $\hat\theta=\theta+\varepsilon h\theta$의 "쿼터니언"이며, 거듭제곱은 각과 이동을 같은 비율로 줄이는 일정한 스크루 운동이다.
$$Q=\cos\frac{\hat\theta}{2}+\sin\frac{\hat\theta}{2}\,\hat s=\Bigl(\cos\tfrac\theta2+\sin\tfrac\theta2\,s\Bigr)+\varepsilon\Bigl(-\tfrac{h\theta}{2}\sin\tfrac\theta2+\tfrac{h\theta}{2}\cos\tfrac\theta2\,s+\sin\tfrac\theta2\,m\Bigr)$$
식은 1.10의 스크루 $(s,q,h,\theta)$와 같은 정보이며, 쿼터니언 부분에서 $\theta=2\operatorname{atan2}(\lVert r_v\rVert,r_w)$, 이중 부분에서 이동 거리 $h\theta=-2d_w/\sin(\theta/2)$와 모멘트 $m$을 읽는다(구현은 1.11.5). $\theta\to0$(순수 이동)에서 $\sin(\theta/2)$로 나누므로 1.10과 같이 복원 오차가 $\varepsilon_{\mathrm{mach}}/\theta$로 커지며 $\theta=0$은 별도로 처리한다.
두 포즈 $Q_0$, $Q_1$ 사이의 스크루 선형 보간(ScLERP)은 상대 변환 $Q_0^*Q_1$의 거듭제곱이다.
$$Q(t)=Q_0\,\bigl(Q_0^*Q_1\bigr)^{t},\qquad t\in[0,1]$$
$Q^t$는 스크루의 $\theta$와 $h\theta$를 $t$배 한 쌍대 쿼터니언이다. $Q_0^*Q_1$의 실수부가 음수이면 긴 경로가 선택되므로 $Q_1$의 부호를 $Q_0$와의 내적이 양수가 되게 맞춘다(쿼터니언의 slerp와 같은 처리, 1.5). ScLERP의 경로는 1.7의 $T_a\exp\bigl(t\,\mathrm{Log}(T_a^{-1}T_b)\bigr)$와 같은 일정한 트위스트의 궤적이다. 이 장은 둘의 일치를 $10^{-15}$ 안에서 확인했다. ScLERP라는 이름의 원 출처는 (확인 필요)이다.
회전은 slerp로, 이동은 선형 보간으로 따로 보간하면 이동 경로가 직선이다. ScLERP의 이동 경로는 일반적으로 원호(나선)이며 두 방법은 양 끝에서만 같다(1.11.5).
1.11.3.4 수치
이중 쿼터니언 곱은 쿼터니언 곱 3회이며 이 장의 구현에서 곱셈 48회, $(R,t)$ 합성은 곱셈 36회이다. 따라서 곱셈 횟수로는 이중 쿼터니언이 유리하지 않다. 곱을 반복하면 제약 $\lVert r\rVert=1$, $r\cdot d=0$이 반올림 오차로 깨지며, 회전 행렬의 직교성 $R^\top R=I$도 같은 크기로 깨진다(1.11.5). 이중 쿼터니언은 $\lVert r\rVert$로 나누고 $d$에서 $r$ 성분을 빼는 정규화로 제약을 되돌린다.
1.11.3.5 등각 기하 대수(CGA)
결론: CGA는 유클리드 점을 영벡터로 넣어 거리를 내적으로 만들고, 구·평면을 벡터로, 강체 변환을 모터 샌드위치로 다룬다. 강체 변환에 해당하는 모터는 이중 쿼터니언의 8개 성분과 같다.
기하 대수는 벡터의 곱(기하곱)이 결합 법칙을 만족하는 대수이다. CGA는 부호수 $(4,1)$의 공간 $\mathbb R^{4,1}$(기저 $e_1,e_2,e_3,e_+,e_-$, $e_+^2=1$, $e_-^2=-1$)에서 $e_\infty=e_-+e_+$, $e_0=\tfrac12(e_--e_+)$로 두며 $e_\infty^2=e_0^2=0$, $e_0\cdot e_\infty=-1$이다. 유클리드 점 $x$를
$$P=x+\tfrac12\,x^2\,e_\infty+e_0,\qquad P\cdot P=0,\qquad P_1\cdot P_2=-\tfrac12\lVert x_1-x_2\rVert^2$$
로 넣는다. 중심 $c$·반지름 $r$의 구는 $S=P_c-\tfrac12r^2e_\infty$, 법선 $n$·원점 거리 $d$의 평면은 $\Pi=n+d\,e_\infty$이고 점이 구·평면 위에 있을 조건이 $P\cdot S=0$, $P\cdot\Pi=0$이다. 변환 이동 $T=1-\tfrac12\,t\,e_\infty$와 회전 $R=\cos\tfrac\theta2-\sin\tfrac\theta2\,B$($B=s_1e_{23}+s_2e_{31}+s_3e_{12}$)의 곱 $M=TR$(모터)가 점을 $P'=MP\tilde M$로 보낸다($\tilde M$는 역순). 모터는 $1,\ e_{12},e_{13},e_{23},\ e_{1\infty},e_{2\infty},e_{3\infty},\ e_{123\infty}$의 8개 성분을 가지며($e_{i\infty}=e_ie_\infty$), 이중 쿼터니언과 다음처럼 대응한다(이 장의 기저·부호 규약에서 유도하고 수치로 확인).
| 이중 쿼터니언 | $q_w$ | $q_x$ | $q_y$ | $q_z$ | $d_w$ | $d_x$ | $d_y$ | $d_z$ |
|---|---|---|---|---|---|---|---|---|
| 모터 계수 | $c_1$ | $-c_{e_{23}}$ | $+c_{e_{13}}$ | $-c_{e_{12}}$ | $-c_{e_{123\infty}}$ | $-c_{e_{1\infty}}$ | $-c_{e_{2\infty}}$ | $-c_{e_{3\infty}}$ |
부호는 기저의 순서·방향과 모터의 정의($R=\cos-\sin B$)에 따라 달라지므로 라이브러리와 맞출 때 확인한다.
1.11.3.6 라이브러리 규약
DQ Robotics. DQ는 8개 성분 vec8()을 $[r_w,r_x,r_y,r_z,\ d_w,d_x,d_y,d_z]$ 순서로 갖고 r + 0.5*E_*t*r로 변환을 구성한다. C++ 소스에서 읽은 log()는 $\tfrac12(\text{회전각})\cdot\text{축}+\varepsilon\cdot\tfrac12\,t$이고, exp()는 $\exp(P+\varepsilon D)=\exp(P)\,(1+\varepsilon D)$ 꼴이며 pow(a)는 exp(a·log())이다. 이 정의는 회전을 $a$승하고 이동을 $a$배 하는 회전·이동 분리 구성이며 스크루의 거듭제곱 $Q^a$가 아니다. 이 장이 실행으로 확인한 두 구성의 일치는 회전만, 이동만, 이동이 축과 평행한 경우(축이 원점을 지남)이고, 이동이 축에 수직한 성분을 가지면 달랐다(1.11.5). 헤더의 문서는 "dual quaternion raised to the power of $a$"라고만 적는다.
gafro. CGA 모터는 기저 scalar, e12, e13, e23, e1i, e2i, e3i, e123i(i는 $\infty$)로 선언된다(Motor.hpp). 기저 이름이 $e_{13}$이어서 $e_{31}$을 사용하는 규약과 부호가 반대이며, gafro의 회전 부호 규약은 이 장이 확인하지 않았다 (확인 필요). 공개 서술은 README가 밝히는 논문 Löw & Calinon(2023)이다.
1.11.4 알고리즘
이중 쿼터니언의 합성·점 변환·ScLERP
dq_from_Rt(R, t): r ← 쿼터니언(R) ; d ← ½ (0,t) r ; Q ← (r, d)
dq_to_Rt(Q): R ← 행렬(r) ; t ← 2 d r*
dq_mul(A, B): (r_A r_B, r_A d_B + d_A r_B)
dq_point(Q, x): Q (1 + e x) Q^3 의 e 부분 = R x + t
dq_screw(Q): σ ← ‖r_v‖ ; θ ← 2 atan2(σ, r_w)
s ← r_v/σ ; hθ ← −2 d_w/σ ; m ← (d_v − ½ hθ r_w s)/σ
dq_pow(Q, t): (θ, hθ) ← (tθ, t hθ)로 쌍대각 형식 재구성
sclerp(A, B, t): B ← −B (r_A·r_B < 0이면) ; A · dq_pow(A* B, t)
CGA 모터는 기하곱 gp와 역순 rev로 정의한다. 구현은 1.11.5의 두 코드 블록에 있다.
1.11.5 풀이 예제 — 이중 쿼터니언·ScLERP·CGA 모터와 라이브러리 확인
이중 쿼터니언의 구성·합성·점 변환·스크루 형식·ScLERP를 $(R,t)$·1.7의 지수와 비교하고, 이중 덮개의 부호와 반복 합성의 제약 표류를 계산하며, CGA의 점·구·평면·모터를 검증하고 이중 쿼터니언과의 대응을 확인한다. 라이브러리(DQ Robotics)는 같은 입력으로 호출한다. 결론부터 적으면 다음과 같다. ① 이중 쿼터니언의 합성·점 변환·스크루 형식·ScLERP는 $(R,t)$와 1.7의 지수·로그에 $1.4\times10^{-15}$ 안에서 일치한다. ② $Q_1$의 부호를 맞추지 않으면 중간점이 긴 경로를 지난다(상대 회전 2.5 rad에서 1.25 rad 대 1.89 rad). ③ 반복 합성 $10^5$회 뒤 쌍대 쿼터니언의 제약 위반은 $6.7\times10^{-14}$, 회전 행렬은 $4.0\times10^{-14}$로 같은 크기이다. ④ CGA 모터의 샌드위치는 $Rx+t$와 $2.2\times10^{-15}$ 안에서 일치하고 모터의 8개 계수는 이중 쿼터니언과 $3.3\times10^{-16}$ 안에서 대응한다. ⑤ DQ Robotics의 pow(a)는 회전·이동 분리 구성이며 일반 스크루에서 X*X와 다르다.
구현. dq_mul·dq_conj·dq_from_Rt·dq_to_Rt·dq_point·dq_from_screw·dq_screw·dq_pow·dq_sclerp·dq_normalize(첫 블록), gp·rev·point·motor·sandwich(둘째 블록). 1.1·1.5·1.7·1.10의 구현
| 항목 | 값 |
|---|---|
| 입력 1 | 무작위 포즈·스크루 1,000개(시드 29): $R$는 무작위 회전 벡터의 지수, $t\in[-1,1]^3$, $h\in[-1,1]$, $\theta\in[0.05,3]$ |
| 입력 2 | 이중 덮개: $Q_0=1$, $Q_1$ = $z$축 회전 $\theta$와 $x$ 이동 0.4 |
| 입력 3 | 반복 합성: 회전 벡터·이동의 표준편차 0.02인 작은 변환 $10^5$개(시드 5) |
| 입력 4 | 보간 도해: 원점에서 $(2.0,\ 0.4)$ m로 가는 $z$축 회전 2.2 rad의 평면 변환 |
import math
def dq_mul(A, B):
"""(r1 + e d1)(r2 + e d2) = r1 r2 + e (r1 d2 + d1 r2)"""
d = [x + y for x, y in zip(qmul(A[:4], B[4:]),
qmul(A[4:], B[:4]))]
return tuple(qmul(A[:4], B[:4])) + tuple(d)
def dq_conj(A):
"""두 부분의 쿼터니언 켤레(단위이면 역원)"""
return tuple(qconj(A[:4])) + tuple(qconj(A[4:]))
def dq_conj3(A):
"""제3 켤레 (r*, -d*): 점 변환의 샌드위치"""
d = qconj(A[4:])
return tuple(qconj(A[:4])) + tuple(-x for x in d)
def dq_from_Rt(R, t):
"""Q = r + (e/2) t r, r = 회전 쿼터니언"""
r = tuple(from_matrix(R))
d = qmul((0.0, *t), r)
return r + tuple(0.5 * x for x in d)
def dq_to_Rt(A):
"""t = 2 d r*"""
t = qmul(A[4:], qconj(A[:4]))
return to_matrix(A[:4]), [2 * x for x in t[1:]]
def dq_point(A, x):
"""Q (1 + e x) Q^3 = 1 + e (R x + t)"""
P = (1.0, 0.0, 0.0, 0.0, 0.0) + tuple(x)
return list(dq_mul(dq_mul(A, P), dq_conj3(A))[5:])
def dq_from_screw(s, q, h, th):
"""쌍대각 θ + e hθ: (cos, sin 쌍대수) 형식"""
c, sg, k = math.cos(th / 2), math.sin(th / 2), h * th / 2
m = cross(q, s)
d = tuple(k * c * a + sg * b for a, b in zip(s, m))
return (c,) + tuple(sg * a for a in s) + (-k * sg,) + d
def dq_screw(A):
"""Q -> (θ, s, hθ, m): 순수 이동이면 s = None"""
sg = math.sqrt(sum(x * x for x in A[1:4]))
if sg < 1e-12:
return 0.0, None, 0.0, None
s = [x / sg for x in A[1:4]]
k = -2 * A[4] / sg
m = [(b - k * A[0] * a / 2) / sg
for a, b in zip(s, A[5:])]
return 2 * math.atan2(sg, A[0]), s, k, m
def dq_pow(A, t):
"""Q^t: 스크루의 각과 이동을 t배"""
th, s, k, m = dq_screw(A)
if s is None:
return (1.0, 0, 0, 0, 0) + tuple(t * x for x in A[5:])
return dq_from_screw_m(s, m, t * k, t * th)
def dq_from_screw_m(s, m, k, th):
c, sg = math.cos(th / 2), math.sin(th / 2)
r = (c,) + tuple(sg * a for a in s)
d = tuple(k / 2 * c * a + sg * b for a, b in zip(s, m))
return r + (-k / 2 * sg,) + d
def dq_sclerp(A, B, t):
"""A (A* B)^t: 부호를 정렬해 최단 경로"""
if sum(x * y for x, y in zip(A[:4], B[:4])) < 0:
B = tuple(-x for x in B)
return dq_mul(A, dq_pow(dq_mul(dq_conj(A), B), t))
def dq_normalize(A):
"""||r|| = 1, r·d = 0으로 되돌린다"""
n = math.sqrt(sum(x * x for x in A[:4]))
r = [x / n for x in A[:4]]
d = [x / n for x in A[4:]]
c = sum(a * b for a, b in zip(r, d))
return tuple(r) + tuple(b - c * a for a, b in zip(r, d))
import math
METRIC = (1, 1, 1, 1, -1) # e1 e2 e3 e+ e-
def blade_mul(a, b):
"""기저 블레이드(비트마스크)의 곱: (부호, 결과)"""
s, x = 1, a >> 1
while x:
if bin(x & b).count('1') % 2:
s = -s
x >>= 1
for i in range(5):
if (a & b) >> i & 1:
s *= METRIC[i]
return s, a ^ b
def gp(A, B):
"""다중벡터(마스크 -> 계수)의 기하곱"""
out = {}
for a, x in A.items():
for b, y in B.items():
s, c = blade_mul(a, b)
out[c] = out.get(c, 0.0) + s * x * y
return out
def rev(A):
"""역순(reverse): 등급 k의 부호 (-1)^(k(k-1)/2)"""
g = lambda m: bin(m).count('1')
return {m: (-1) ** (g(m) * (g(m) - 1) // 2) * c
for m, c in A.items()}
def add(A, B, k=1.0):
out = dict(A)
for m, c in B.items():
out[m] = out.get(m, 0.0) + k * c
return out
E_INF, E_ZERO = {8: 1.0, 16: 1.0}, {8: -0.5, 16: 0.5}
def vec3(x):
return {1 << i: x[i] for i in range(3)}
def point(x):
"""P = x + (x.x/2) e_inf + e_0 (영벡터)"""
return add(add(vec3(x), E_INF, 0.5 * dot(x, x)), E_ZERO)
def motor(s, th, t):
"""M = T R: T = 1 - (t/2) e_inf, R = cos - sin B"""
B = {6: s[0], 5: -s[1], 3: s[2]}
R = add({0: math.cos(th / 2)}, B, -math.sin(th / 2))
return gp(add({0: 1.0}, gp(vec3(t), E_INF), -0.5), R)
def sandwich(M, P):
"""P' = M P M~ -> 유클리드 점"""
Q = gp(gp(M, P), rev(M))
w = -gp(Q, E_INF).get(0, 0.0)
return [Q.get(1 << i, 0.0) / w for i in range(3)]
① 이중 쿼터니언의 검증.
| 항목 | 최대 오차 | 비고 |
|---|---|---|
| 제약 $\lVert r\rVert=1$, $r\cdot d=0$ | 2.2e-16 | $Q=r+\tfrac{\varepsilon}{2}tr$, 무작위 1,000개 |
| $(R,t)\to Q\to(R,t)$ 왕복 | 7.8e-16 | 같은 1,000개 |
| 합성 $Q_1Q_2$ 대 $(R_1R_2,\ R_1t_2+t_1)$ | 1.3e-15 | 1,000쌍 |
| 점 변환 $Q(1+\varepsilon x)Q^{\,3}$ 대 $Rx+t$ | 7.8e-16 | 제3 켤레의 샌드위치 |
| 쌍대각 형식 $\cos\tfrac{\hat\theta}{2}+\sin\tfrac{\hat\theta}{2}\hat s$ 대 지수 | 6.7e-16 | $\hat\theta=\theta+\varepsilon h\theta$ |
| 쌍대 쿼터니언에서 $(\theta,s,h\theta,q)$ 복원 | 1.3e-15 | $\theta\in[0.05,3]$, 실수부 부호를 양으로 맞춤 |
| ScLERP 대 $T_a\exp(t\,\mathrm{Log}(T_a^{-1}T_b))$ | 1.4e-15 | $t=0.25,0.5,0.75$, 상대 회전 3 rad 미만 |
| 곱셈 횟수: 쌍대 쿼터니언 곱 대 $(R,t)$ 합성 | 48회 대 36회 | 이 장의 구현에서 센 값 |
② CGA의 검증.
| 항목 | 최대 오차 | 비고 |
|---|---|---|
| 점의 영벡터성 $\lvert P\cdot P\rvert$ | 6.7e-16 | $P=x+\tfrac12x^2e_\infty+e_0$ |
| 거리 $P_1\cdot P_2+\tfrac12\lVert x_1-x_2\rVert^2$ | 8.9e-16 | 무작위 1,000쌍 |
| 구 위의 점 $P\cdot S$ | 8.9e-16 | $S=c+\tfrac12(c^2-r^2)e_\infty+e_0$ |
| 평면 위의 점 $P\cdot\Pi$ | 6.9e-16 | $\Pi=n+d\,e_\infty$ |
| 모터 샌드위치 $MP\tilde M$ 대 $Rx+t$ | 2.2e-15 | 무작위 1,000개 |
| $e_i e_+$와 $e_i e_-$ 계수의 일치(모터가 $e_\infty$ 부분대수에 속함) | 0.0e+00 | $i=1,2,3$과 $e_{123}$ |
| 모터 8계수 대 단위 쌍대 쿼터니언 | 3.3e-16 | 아래 대응표 |
③ 이중 덮개와 최단 경로. $Q_0=1$에서 $Q_1$까지 중간점($t=0.5$)의 회전각이다. 최단 경로는 상대 회전각의 절반이다.
| 상대 회전각 (rad) | 최단 경로의 중간점 회전각 | 부호를 맞추지 않은 중간점 회전각($-Q_1$) |
|---|---|---|
| 1 | 0.5000 | 2.6416 |
| 2.5 | 1.2500 | 1.8916 |
| 3 | 1.5000 | 1.6416 |
부호를 맞추지 않으면 $-Q_1$을 받은 거듭제곱이 반대 방향의 긴 경로로 간다. 상대 회전각 1 rad에서 중간점 회전각이 0.5 rad 대신 2.64 rad이다.
④ 보간 경로.
ScLERP의 이동 경로는 일정한 스크루의 궤적이어서 원호이고, 회전을 slerp로 이동을 선형으로 따로 보간하면 직선이다. 두 경로의 양 끝은 같다.
⑤ 반복 합성의 제약 표류. 작은 변환 $10^5$개를 순서대로 곱했을 때 제약 위반이다(정규화 없음).
| 합성 횟수 | $\lvert\lVert r\rVert-1\rvert$ | $\lvert r\cdot d\rvert$ | $\max\lvert R^\top R-I\rvert$ | $\lvert\det R-1\rvert$ |
|---|---|---|---|---|
| 1000 | 0.0e+00 | 1.4e-17 | 2.9e-15 | 1.8e-15 |
| 10000 | 1.4e-14 | 3.1e-16 | 1.2e-14 | 1.1e-14 |
| 100000 | 6.7e-14 | 6.0e-14 | 4.0e-14 | 2.1e-14 |
| 정규화 후(쌍대 쿼터니언) | 0.0e+00 | 1.7e-16 |
쌍대 쿼터니언과 회전 행렬 모두 합성 횟수에 따라 $10^{-16}$에서 $10^{-14}$ 수준으로 커지며 어느 쪽이 유리한지는 이 입력에서 구분되지 않는다. 정규화는 쌍대 쿼터니언의 두 제약을 한 번에 되돌린다.
⑥ DQ Robotics 호출 결과(실행 2026-11-03). 스크립트는 robotics/fig-src/1.11-libcheck.py·1.11-libcheck-stable.py, 출력은 robotics/fig-src/data/1.11-libcheck-output.txt에 있다.
| 항목 | 사전 릴리스 26.4.0a7 | 안정 릴리스 20.4.0.79 |
|---|---|---|
r + 0.5*E_*t*r의 vec8() 순서·값 최대 차이 | 0.0e+00 | — |
곱 X*Y 최대 차이 | 2.2e-16 | — |
translation() 최대 차이 | 3.3e-16 | — |
rotation_angle() 최대 차이 | 1.9e-15 | — |
pow(t) 대 회전 거듭제곱·이동 $t$배 분리 구성 | 4.4e-16 | — |
pow(2) 대 X*X, 축이 원점을 지나는 경우 | — | 0.0e+00 |
pow(2) 대 X*X, 축이 원점을 지나지 않는 경우(회전 $z$, 이동 $x$ 0.5) | — | 2.10e-01 |
pow(2) 대 X*X, 일반 경우(이동 $(0.5,0.2,0.3)$) | — | 2.56e-01 |
pow(2) 대 X*X, 무작위 포즈(실수부 양수)의 최대 | 1.2e+00 | — |
사전 릴리스에서도 같은 두 일반 경우가 같은 값(0.210, 0.256)이다. pow(t)는 이 장의 회전 거듭제곱·이동 $t$배 구성과 $4.4\times10^{-16}$ 안에서 같으므로 소스에서 읽은 정의(1.11.3.6)와 일치한다. 부호가 반대인 입력($-Q$)에서 이 장의 pow와 라이브러리의 pow(−Q, 0.5)는 같은 긴 경로의 회전각(상대 회전 1 rad에서 2.6416 rad, 2.5 rad에서 1.8916 rad)을 낸다. 곧 라이브러리 pow도 부호를 맞추지 않는다.
⑦ 라이브러리 저장소(GitHub API, 조회 2026-11-03).
| 라이브러리 | 저장소 | 라이선스(GitHub) | 최신 릴리스 | 마지막 푸시 |
|---|---|---|---|---|
| DQ Robotics(Python) | dqrobotics/python | LGPL-3.0 | v20.04.0·2020-05-07 | 2026-09-28 |
| DQ Robotics(C++) | dqrobotics/cpp | LGPL-3.0 | 릴리스 없음 | 2026-09-21 |
| gafro | idiap/gafro | MPL-2.0 | 릴리스 없음 | 2026-01-30 |
푸시 날짜는 유지의 대리 지표일 뿐이다(0.18). gafro는 소스(Motor.hpp)와 README만 읽었고 실행하지 않았다.
한계. 수치는 합성 입력(시드 고정)이다. 스크루 복원은 $\theta\in[0.05,3]$에서만 검증했고 $\theta\to0$의 오차는 측정하지 않았다(1.10의 $\varepsilon_{\mathrm{mach}}/\theta$와 같은 원인이 예상된다 (추정)). 보간 도해는 평면 한 쌍의 포즈이다. CGA는 점·구·평면과 모터만 구현했고 직선·원·역변환 등은 구현하지 않았다. 라이브러리 확인은 DQ Robotics의 pow·곱·변환 추출에 한정했으며 log·exp·야코비안·기구학은 실행하지 않았다. 안정 릴리스 확인은 Python 3.9·NumPy 1.26.4(NumPy 2.x에서는 호출이 비정상 종료)이다.
견해: 이 책은 포즈 보간에서 쿼터니언과 이동을 따로 보간하는 방법과 ScLERP 중 어느 쪽인지 사용 전에 라이브러리의 정의를 확인하고, 일정한 스크루 운동이 필요하면 ScLERP를 사용하며, 이동을 직선으로 두어야 하는 작업공간 제약이 있으면 분리 보간을 사용하는 것을 기본으로 둔다고 본다. 근거는 위 예제에서 두 경로가 양 끝만 같고, DQ Robotics의
pow가 이름과 달리 분리 보간임이 실행으로 확인되었다는 점이다.
1.11.6 설계 트레이드오프 — 변환의 표현
공개 문헌에서 확인되는 선택지는 네 가지다.
- ① 행렬 $4\times4$ 또는 $(R,t)$. 널리 지원되며 변환·점 작용이 가장 단순하다(1.6).
- ② 쿼터니언과 이동 벡터. 회전만 4개 수로 줄이고 이동은 벡터로 둔다(1.5). slerp와 선형 보간으로 분리 보간한다.
- ③ 단위 이중 쿼터니언. 8개 성분으로 변환을 합치고 ScLERP를 제공한다.
- ④ CGA 모터. 점·구·평면과 같은 대수에서 다룬다.
| 방식 | 장점 | 한계 | 알맞은 경우 |
|---|---|---|---|
| ① 행렬·$(R,t)$ | 단순, 생태계 지원 | 제약 6개, 스크루 보간에 로그 필요 | 변환 체인·기구학·좌표 변환 |
| ② 쿼터니언+벡터 | 회전 4수, slerp | 이동은 분리 | 자세 보간, 센서 융합 |
| ③ 이중 쿼터니언 | 합성이 곱, ScLERP, 8수 | 곱셈 36회 대 48회, 라이브러리 정의 차이 | 스크루 보간, 자코비안의 통합 표현 |
| ④ CGA 모터 | 점·구·평면의 통합 | 학습·도구 비용, 라이브러리 규약 | 기하 제약을 대수로 표현하는 연구·최적 제어 |
견해: 한 코드베이스에서 변환의 내부 표현을 하나로 두고 경계에서 변환하며, 보간이 필요한 곳에서만 ScLERP를 사용하는 것이 표현 선택 자체보다 중요하다고 본다. 근거는 이 장에서 표현을 바꿔도 합성·점 변환이 $10^{-15}$ 안에서 같았고, 차이는 보간의 정의와 부호 처리에서 나타났다는 점이다.
1.11.7 응용 축 비교
| 응용 축 | 이중 쿼터니언·기하 대수가 맡는 일 | 요구 | 관련 장 |
|---|---|---|---|
| 산업·제조 | 포즈 보간, 손-눈 보정 | 정확도 | 2.4, 2.8 |
| 물류·이동 | 매니퓰레이터 포즈 궤적의 보간 | 연산량 | 2.4 |
| 도로 주행 | 해당하는 고유 요구 없음 | — | — |
| 서비스 | 모방 학습 궤적의 포즈 보간 | 부드러운 운동 | 1.10 |
| 의료·헬스케어 | 영상 정합과 수술 내비게이션의 포즈 | 정밀도, 검증 | 32.7 |
| 휴머노이드·범용 | 다관절 기구학의 통합 표현, 최적 제어 | 수치 안정 | 3.5 |
| 필드 | 해당하는 고유 요구 없음 | — | — |
| 국방·안보 | 원리 수준에서 해당하는 고유 요구 없음 | — | — |
이 표는 이 책의 정리이며 장별 요구는 다르다. 손-눈 보정에 이중 쿼터니언을 사용하는 방법은 Daniilidis(1999)가, 컴퓨터 그래픽스의 스키닝에 사용하는 방법은 Kavan 외(2007·2008)가 다룬다(서지 확인, 본문 미확인).
1.11.8 관련 규격·프로토콜
| 문서 | 발행 | 무엇을 정하나 | 상세 장 |
|---|---|---|---|
| 「DQ Robotics: A Library for Robot Modeling and Control」 | Adorno·Marinho, IEEE RAM 28(3), 2021 | 쌍대 쿼터니언 기반 로봇 모델링·제어 라이브러리 | — |
| 「Geometric Algebra for Optimal Control With Applications in Manipulation Tasks」 | Löw·Calinon, IEEE T-RO 39(5), 2023(arXiv:2212.07237) | CGA 기반 최적 제어와 gafro | — |
| 「Hand-Eye Calibration Using Dual Quaternions」 | Daniilidis, IJRR 18(3), 1999 | 쌍대 쿼터니언의 손-눈 보정 | — |
| DQ Robotics(Python·C++) | GitHub dqrobotics(LGPL-3.0) | 쌍대 쿼터니언 라이브러리 | — |
| gafro | GitHub idiap/gafro(MPL-2.0) | CGA 기반 C++ 라이브러리 | — |
| 수학 표기·기호표 | 이 책 | 쿼터니언·쌍대수 표기 | A.2 |
이중 쿼터니언·CGA의 표기에 국제 표준은 없다. 성분 순서·곱의 정의·켤레의 정의는 문헌과 라이브러리가 정하므로 라이브러리의 소스를 읽는 것이 상호운용의 기준이다.
1.11.9 관련 규제·인증 요구와의 연결
직접 적용되는 규제는 없다. 보간·변환 함수의 정의 차이(분리 보간과 스크루 보간의 혼동)는 궤적 생성의 결과를 바꾸므로 안전 기능이 의존하는 경로의 검증 항목이 될 수 있다. 산업 로봇의 안전 표준은 27.10, 소프트웨어 안전과 품질 표준은 26.20이 다룬다.
1.11.10 한계·유의점·미해결
- 정리의 출처. ScLERP의 원 출처, 쌍대 쿼터니언·CGA의 정리 번호, 교과서 장 번호(Selig·Dorst 외)는 확인하지 않았다
(확인 필요). - gafro. 소스·README만 읽었고 실행하지 않았다. 회전·이동의 부호 규약과 이 장의 모터 대응(1.11.3.5)의 일치는 확인하지 않았다
(확인 필요). - DQ Robotics.
pow가 분리 구성인 것은 소스와 실행으로 확인했으나log·exp의 다른 사용(예: 단위 쌍대 쿼터니언의norm비교 허용 오차)은 실행하지 않았다. 문서의 설명은 읽지 않았다. - 작은 각. $\theta\to0$에서 스크루 복원의 오차는 측정하지 않았다.
- 블렌딩. 쌍대 쿼터니언 선형 블렌딩(DLB)과 스키닝의 인공 효과는 다루지 않았다(Kavan 외).
1.11.11 체크포인트·연구 질문
체크포인트:
- 이중수 $\varepsilon^2=0$에서 쌍대 쿼터니언의 곱을 적고 $Q=r+\tfrac\varepsilon2tr$에서 $(R,t)$를 복원할 수 있는가.
- 단위 조건 두 가지와 $Q$, $-Q$의 관계를 설명할 수 있는가.
- 점 변환의 샌드위치에서 제3 켤레를 써야 하는 이유를 유도할 수 있는가.
- 쌍대각 형식에서 스크루 $(s,q,h,\theta)$를 읽을 수 있는가.
- ScLERP를 정의하고 최단 경로를 위해 부호를 맞추는 이유와 분리 보간과의 경로 차이를 설명할 수 있는가.
- CGA의 점 임베딩에서 거리 내적을 유도하고 모터를 이중 쿼터니언과 대응시킬 수 있는가.
- 라이브러리의
pow가 스크루 거듭제곱인지 분리 구성인지 어떻게 확인하는가.
연구 질문(47.12에 수록한다):
- 로봇 궤적 계획에서 ScLERP와 분리 보간 중 어느 쪽이 작업공간 제약 위반을 적게 만드는가, 그 차이는 어느 이동·회전 크기에서 나타나는가.
- 쌍대 쿼터니언 라이브러리의
pow·log·exp·ScLERP의 정의는 라이브러리마다 일치하는가, 정의 차이는 실제 코드에서 오류로 보고되는가. - gafro·DQ Robotics 사이의 모터·쌍대 쿼터니언 변환(부호·성분 순서)은 어떻게 표준화할 수 있는가.
1차 출처
- B. V. Adorno, M. M. Marinho, 「DQ Robotics: A Library for Robot Modeling and Control」, IEEE Robotics & Automation Magazine 28(3), 102–116, 2021(doi:10.1109/mra.2020.2997920) — 서지만 확인, 본문 미확인.
- T. Löw·S. Calinon, 「Geometric Algebra for Optimal Control With Applications in Manipulation Tasks」, IEEE Transactions on Robotics 39(5), 3586–3600, 2023(doi:10.1109/tro.2023.3277282; arXiv:2212.07237).
- K. Daniilidis, 「Hand-Eye Calibration Using Dual Quaternions」, International Journal of Robotics Research 18(3), 286–298, 1999(doi:10.1177/02783649922066213).
- L. Kavan, S. Collins, J. Žára 외, 「Geometric skinning with approximate dual quaternion blending」, ACM Transactions on Graphics 27(4), 1–23, 2008(doi:10.1145/1409625.1409627); 「Skinning with dual quaternions」, Proceedings of I3D, 39–46, 2007(doi:10.1145/1230100.1230107)
- J. M. Selig, 『Geometric Fundamentals of Robotics』, 2판, Springer, 2005(doi:10.1007/b138859)
(확인 필요: 판·장 번호). - L. Dorst·D. Fontijne·S. Mann, 『Geometric Algebra for Computer Science』, Elsevier(Morgan Kaufmann), 2007
(확인 필요: 장 번호). - GitHub API·소스(조회·실행 2026-11-03):
dqrobotics/cpp(DQ.cpp·DQ.h),dqrobotics/python(dqrobotics26.4.0a7·20.4.0.79 실행),idiap/gafro(Motor.hpp·README.md).
다음 장. 1.12은 최적화 I로, 비선형 최소제곱의 가우스-뉴턴·레벤버그-마콰트·강건 손실을 다룬다. 이 장까지의 자세·변환의 표현과 접공간 갱신을 목적 함수의 최소화로 연결하고, 자세 그래프·보정 문제의 반복 갱신을 정리한다.