Pinocchio에서 q는 로봇의 현재 배치를 저장하고, v는 그 배치가 변하는 속도를 나타냅니다. 두 배열은 길이부터 다를 수 있습니다. 다음 배치를 구할 때는 q + v처럼 더하기보다, 모델의 관절 표현에 맞는 integrate 연산에 시간 간격을 반영한 변화량을 넣어야 합니다.
고정된 축을 따라 움직이는 관절만 보다가 자유롭게 이동하고 회전하는 베이스를 추가하면 이 차이가 드러납니다. 배열 길이 오류를 없애려고 속도 뒤에 숫자를 하나 덧붙여도 올바른 상태 갱신이 되지는 않습니다. 무엇을 저장하는 좌표인지부터 달라졌기 때문입니다.
자세를 저장하는 수와 움직일 수 있는 방향의 수
로봇의 configuration은 관절과 베이스를 포함한 전체 배치입니다. 여기서는 이를 자세 좌표라고 부르겠습니다. 회전과 위치를 어떤 방식으로 표현하느냐에 따라 저장할 숫자의 수가 달라집니다. 반면 tangent vector, 즉 접공간 벡터는 현재 배치에서 가능한 작은 변화의 방향과 크기를 나타냅니다.
Pinocchio의 관절 설명은 관절마다 configuration 공간과 tangent 공간이 있으며, 일부 configuration 공간에서는 보통 벡터처럼 덧셈과 뺄셈을 할 수 없다고 설명합니다. 그래서 배치에 변화를 적용하는 적분 연산과, 두 배치 사이의 변화를 구하는 차이 연산을 제공합니다.
예를 들어 자유롭게 움직이는 물체를 free-flyer 관절 하나로 표현한다고 하겠습니다. 확인한 free-flyer 소스의 좌표 정의는 다음과 같습니다.
| 항목 | 이 관절에 필요한 숫자 수 | 담고 있는 정보 |
|---|---|---|
자세 좌표 q |
7 | 위치 3개와 단위 쿼터니언 4개 |
속도 v |
6 | 선속도 3개와 각속도 3개 |
| 작은 자세 변화 | 6 | 이동 변화 3개와 회전 변화 3개 |
쿼터니언에 숫자가 네 개 있다고 회전 자유도가 네 개인 것은 아닙니다. 단위 길이 조건을 갖는 회전 표현이므로, 회전의 작은 변화는 세 성분으로 나타냅니다. 따라서 이 관절에서는 q와 v를 같은 길이의 배열로 취급할 수 없습니다. 로봇 전체의 크기는 추가된 관절 표현에 따라 정해지므로, 모든 로봇에서 두 길이가 반드시 다르다는 뜻도 아닙니다.
integrate는 속도를 알아서 시간으로 바꾸지 않는다
현재 소스의 integrate 선언은 입력 자세의 크기를 model.nq, 접공간 입력의 크기를 model.nv, 출력 자세의 크기를 다시 model.nq로 구분합니다. 설명에 나오는 단위 시간 적분은 함수가 제어 주기를 자동으로 읽는다는 뜻이 아닙니다.
속도 v를 짧은 시간 Δt 동안 일정하게 적용하려면, 의미상 흐름은 아래와 같습니다. 실행한 코드가 아니라 입력과 출력의 관계를 나타낸 표기입니다.
자세 변화량 = v × Δt # nv 성분
다음 자세 = integrate(model, q, 자세 변화량) # nq 성분
선속도에 초 단위 시간을 곱하면 이동량이 되고, 각속도에 같은 시간을 곱하면 회전 변화량이 됩니다. 이미 시간 간격이 반영된 변화량에 다시 Δt를 곱하면 움직임을 한 번 더 줄이는 셈입니다. 로그나 최적화 결과에 v라는 이름이 붙어 있다는 이유만으로 그것을 실제 초당 속도라고 판단해서도 안 됩니다.
이 연산은 관절 표현을 따라 배치를 갱신합니다. 질량·토크·접촉력으로 다음 속도를 계산하는 동역학 풀이까지 대신하지는 않습니다. 자세에서 토크로 넘어가는 계산은 역기구학과 역동역학의 차이와 구분해 읽으면 됩니다.
배열 크기가 맞아도 좌표계가 틀릴 수 있다
free-flyer의 위치 세 성분은 부모 좌표계에서 본 자식 좌표계의 위치입니다. 확인한 소스에서 쿼터니언은 자식에서 부모로의 회전을 나타내며, 저장 순서는 (x, y, z, w)입니다. 반면 선속도와 각속도는 자식 좌표계로 표현한 body velocity로 정의되어 있습니다.
따라서 세계 좌표계에서 만든 선속도를 이 벡터에 그대로 복사하는 것은 배열 길이만 맞춘 상태일 수 있습니다. 베이스가 돌아가 있으면 세계의 앞쪽과 베이스 자신의 앞쪽은 다른 방향입니다. 외부 추정기에서 속도를 가져올 때는 그 값의 기준 좌표계가 관절 모델의 정의와 맞는지 확인해야 합니다.
이 점은 쿼터니언 네 성분의 순서를 맞추는 문제와도 별개입니다. 자세 배열을 정확하게 읽었더라도, 속도 벡터가 다른 좌표계를 쓰면 의도한 움직임이 만들어지지 않습니다.
두 자세의 차이는 다시 접공간으로 돌아온다
같은 소스에서 difference(model, q0, q1)은 초기 배치 q0에서 목표 배치 q1로 가기 위해 단위 시간 동안 적분할 접공간 벡터를 구하는 연산으로 설명됩니다. 두 입력은 nq 크기이고 결과는 nv 크기입니다. 이 결과를 곧바로 실측 속도라고 부르면 안 됩니다. 관측 사이에 얼마의 시간이 흘렀는지와 어떤 변화 모델을 적용할지는 별도 정보입니다.
이 구별은 최적화에서도 중요합니다. 자유 베이스만 있는 모델에서 상태를 자세 q와 속도 v로 묶으면 저장할 숫자는 7 + 6 = 13개입니다. 그러나 이 상태를 조금 바꾸는 변수는 자세 변화 6개와 속도 변화 6개, 총 12개로 구성할 수 있습니다. 이 설명용 계산에서 저장 공간은 13차원, 작은 상태 변화를 나타내는 공간은 12차원입니다.
Crocoddyl의 States 설명도 상태가 놓이는 공간과 상태 변화가 놓이는 접공간을 나누고, integrate와 difference로 둘을 연결합니다. 따라서 상태 벡터를 저장한 길이를 그대로 최적화 변화량의 길이로 쓰면, 잘못된 크기의 미분이나 갱신식을 만들 수 있습니다. 노트북의 수학 표기와 개별 Python API의 인자 순서를 같은 것으로 추정하지 말고, 실제 사용하는 함수의 선언을 기준으로 연결해야 합니다.
Pinocchio 모델을 바꾼 뒤 상태 갱신이 어긋난다면 nq와 nv를 억지로 맞추기보다 각 배열의 역할을 먼저 보세요. 현재 배치인지, 속도인지, 이미 시간이 반영된 변화량인지가 정해져야 올바른 연산과 좌표계를 선택할 수 있습니다.
자료 확인: 2026년 10월 15일. Pinocchio는 commit 700472f50e3349d81860b48b174a9f97d26fa7d9의 선언과 주석을 읽었습니다. 라이브러리 실행·로봇 제어·수치 적분 시험은 수행하지 않았습니다.