Crocoddyl의 궤적과 피드백 이득: calc·calcDiff에서 K까지

Crocoddyl에서 최적화하는 궤적은 로봇이 지나갈 위치 목록보다 넓은 개념입니다. 시간 단계별 상태 궤적과 제어 입력 궤적, 그리고 그 주변의 상태 변화에 대응하는 피드백 이득을 함께 읽어야 합니다. 공식 소개도 최적 궤적과 피드백 이득 계산을 라이브러리의 핵심 기능으로 설명합니다.

이를 이해하는 출발점은 action model의 calc와 calcDiff입니다. 두 메서드가 준비한 값과 미분 정보는 최적화의 수정 방향 계산에 쓰이고, solver는 그 방향으로 새 궤적을 시험합니다. 아래에서는 공식 입문 문서의 개념을 먼저 정리한 뒤, 현재 FDDP 코드가 기준 입력에 k와 K를 적용하는 과정을 따라갑니다.

한 단계의 결과와 그 결과가 변하는 방향

Action model은 최적 제어 문제의 한 노드에서 동역학과 비용을 묶습니다. 현재 상태와 제어 입력을 주었을 때 다음 상태가 어떻게 되는지, 그 선택에 얼마의 비용이 드는지를 표현하는 단위입니다. 공식 입문 노트북의 Action Models 절은 계산을 다음처럼 나눕니다.

calc는 주어진 상태·입력에서 다음 상태, 비용값, 제약 관련 값을 계산합니다. 예를 들어 현재 후보 입력으로 한 단계 진행했을 때의 결과를 평가하는 역할입니다. calcDiff는 동역학·비용·제약의 미분 정보를 계산합니다. 입력이나 상태를 조금 바꾸면 결과가 어느 방향으로 얼마나 달라지는지를 solver가 이용할 수 있게 합니다.

두 호출이 다루는 정보는 data 객체에 저장됩니다. 모델은 계산할 문제를 정의하고, data는 그 계산에 쓰이는 결과와 미분 정보를 담습니다. 입문 문서는 createData로 data 객체를 만들며, 반복 계산 중 동적 메모리 재할당을 피하는 것이 이 설계의 목적이라고 설명합니다.

비용값 하나와 비용의 미분은 용도가 다릅니다. 현재 비용이 작거나 크다는 사실만으로 다음 입력을 어느 방향으로 바꿀지는 정해지지 않습니다. 반대로 미분으로 만든 수정 방향은 현재 궤적 주변의 정보를 이용하므로, 새 후보에서 다시 동역학과 비용을 평가하는 단계가 필요합니다. 이 연결이 calc와 calcDiff를 따로 둔 이유를 이해하는 데 도움이 됩니다.

FDDP가 구분하는 작은 k와 큰 K

여기서 코드 근거로 삼는 대상은 커밋 babab496116e883960a9203df8fbac25a05116ee의 FDDP 구현입니다. 특히 computePolicy와 singleShootForwardPass를 연결해 읽습니다. 이 글의 입력 갱신식은 이 single-shooting 경로를 기준으로 합니다.

computePolicy는 K_[t]와 k_[t]를 각각 계산합니다. 코드에서는 같은 Quu 행렬의 분해 결과를 이용하되, K에는 Qxu의 전치를, k에는 Qu를 우변으로 넣어 선형계를 풉니다. 그 결과 두 객체는 크기부터 다릅니다.

  • k[t]는 해당 단계의 제어 입력 차원 nu만큼의 원소를 가진 벡터입니다. 시험할 제어 입력에 적용하는 feedforward 수정량입니다.
  • K[t]는 nu × ndx 행렬입니다. 상태의 국소 변화량 ndx개를 받아 제어 입력 수정량 nu개로 바꿉니다.

따라서 K의 열 개수는 저장된 상태 배열의 길이라고 바로 해석하기보다, 모델이 정의한 상태 차이의 차원으로 읽어야 합니다. 로봇 자세처럼 좌표 표현에 구조가 있는 상태에서는 두 상태의 차이를 계산하는 연산이 따로 있습니다.

실제로 singleShootForwardPass는 기준 상태 xs_[t]와 시험 상태 xs_try_[t]를 상태 객체의 diff에 넣습니다. 이 차이를 사용해 시험 제어 입력을 만들고, 그 입력으로 calc를 호출합니다. 원래 C++ 코드의 흐름을 수식에 가까운 표기로 줄이면 다음과 같습니다. 아래는 실행용 Python API가 아닌 연산 설명입니다.

상태 차이:  dx = diff(기준 상태, 시험 상태)
시험 입력:  u_try = u_nominal - alpha * k - K * dx
다음 상태:  action model의 calc 결과인 xnext

alpha는 이번 시험에 적용할 step length입니다. 이 경로에서는 k 항에 곱해지며, K * dx 항에는 별도로 곱해지지 않습니다. K가 주는 보정량은 이미 이번 시험 상태가 기준 상태에서 얼마나 달라졌는지에 따라 계산됩니다.

calc 뒤에는 data의 xnext를 다음 시험 상태로 넘기고, 비용을 누적합니다. 시간 단계마다 상태와 입력을 잇는 과정이므로, K만 따로 떼어 고정된 하나의 PD 이득처럼 보는 것보다 기준 궤적의 해당 단계에 연결된 행렬로 이해하는 편이 정확합니다.

같은 기준 입력이라도 시험 상태가 달라지면

다음은 코드의 부호와 항의 역할을 읽기 위한 무차원 가상 1성분 계산입니다. 기준 입력을 0.8, k를 0.2, alpha를 0.5, K를 2라고 놓겠습니다.

시험 상태의 차이 dx alpha × k K × dx 계산한 시험 입력
0 0.1 0 0.8 − 0.1 = 0.7
0.05 0.1 0.1 0.8 − 0.1 − 0.1 = 0.6

첫 행은 시험 상태가 기준 상태와 같아서 피드백 항이 0인 경우입니다. 그래도 k에 따른 수정이 남아 기준 입력 0.8에서 0.7로 바뀝니다. 두 번째 행은 같은 기준 입력과 같은 k를 쓰지만 상태 차이가 생겨 K * dx가 추가됩니다.

이 예에서 볼 수 있듯 k와 K는 이름의 대소문자만 다른 동일 정보가 아닙니다. k는 이번 궤적 개선 방향의 입력 성분이고, K는 기준 궤적에서 벗어난 상태에 따른 입력 변화를 연결합니다. 또 여기서 사용한 빼기 부호는 해당 구현의 diff 인자 순서와 함께 읽어야 합니다. 다른 제어 코드에 식을 옮기면서 상태 오차의 정의만 반대로 바꾸면 보정 방향도 달라집니다.

solver의 시험 궤적을 제어 루프로 연결할 때

지금 읽은 xs_try는 최적화 안에서 시험하는 상태입니다. 이 함수는 센서에서 실제 로봇 상태를 읽는 대신, 시작 상태에서 calc가 돌려준 다음 상태를 따라 시험 궤적을 만듭니다. 코드에 피드백 항이 있다고 해서 이 함수 자체가 센서·구동기 루프인 것은 아닙니다.

제어 시스템으로 연결할 때는 어떤 시간 단계의 기준 상태·입력·이득을 사용할지, 관측한 상태를 같은 상태 표현으로 어떻게 가져올지, 문제에서 정의한 제어 입력을 어느 구동 명령에 대응시킬지가 이어져야 합니다. 특히 u는 action model이 정의한 입력입니다. 모든 모델에서 동일한 형태의 관절 토크라고 가정하면 문제 정의와 실행 측의 연결을 잘못 읽을 수 있습니다.

현재 상태를 기준으로 미래 구간을 다시 풀고 그 결과를 이용하는 MPC의 큰 흐름은 휴머노이드 균형 제어의 ZMP·캡처 포인트·MPC 설명과 연결됩니다. Crocoddyl 소스에서 추가로 확인할 부분은 그 미래 궤적을 만드는 동안 값 계산, 미분, 입력 수정, 재평가가 어떻게 이어지는가입니다. 이 구분을 갖고 보면 상태 궤적과 제어 궤적을 그린 그림 뒤에 어떤 계산이 있는지도 읽을 수 있습니다.

이 글은 2026년 10월 15일 확인한 공식 노트북·소개 문서와 위 커밋의 FDDP 소스를 해석했습니다. Solver를 실행하거나 실제 로봇에 이득을 적용하지는 않았습니다.