acados의 RTI(real-time iteration)는 계산을 준비 단계와 피드백 단계로 나누어, 최신 상태를 반영한 제어 입력을 내기까지의 지연을 줄이는 방식입니다. 이 구조를 읽을 때 핵심은 solve()를 두 번 호출한다는 사실보다, 현재 상태를 두 호출 사이의 어디에 넣는가입니다.
공식 closed-loop 예제는 준비 계산을 먼저 수행하고, 현재 시뮬레이션 상태를 초기 상태 제약에 넣은 뒤, 피드백 계산으로 첫 제어 입력을 구합니다. 준비 시간과 피드백 시간도 각각 기록합니다. 이 순서를 알면 RTI의 두 단계, 계산한 제어 입력의 의미, 시간 통계를 같은 기준으로 해석할 수 있습니다.
최신 상태를 넣는 자리를 분리한다
acados 기능 문서는 RTI의 준비·피드백 분할 목적을 제어 지연 감소로 설명합니다. 공식 AS-RTI closed-loop 예제는 RTI와 여러 AS-RTI 설정을 같은 틀로 실행하며, 단계 선택에 rti_phase를 사용합니다.
main 반복문에서 RTI 계열 경로의 순서는 다음과 같습니다. 아래는 시간 기록과 단계 전환에 관련된 부분을 추린 코드입니다. ocp_solver, simX, simU와 시간 배열은 앞에서 만들어져 있다고 전제합니다.
ocp_solver.options_set('rti_phase', 1)
status = ocp_solver.solve()
t_preparation[i] = ocp_solver.get_stats('time_tot')
ocp_solver.set(0, 'lbx', simX[i, :])
ocp_solver.set(0, 'ubx', simX[i, :])
ocp_solver.options_set('rti_phase', 2)
status = ocp_solver.solve()
t_feedback[i] = ocp_solver.get_stats('time_tot')
simU[i, :] = ocp_solver.get(0, 'u')
rti_phase=1의 호출은 준비 단계이고, rti_phase=2는 피드백 단계입니다. 그 사이에 들어가는 두 set 호출이 이번 반복의 현재 상태를 반영합니다. Stage 0의 하한 lbx와 상한 ubx를 같은 simX[i, :]로 설정해 초기 상태를 그 값에 고정하는 구조입니다.
이 예제의 simX[i, :]는 배열에 저장된 시뮬레이션 상태이며, 두 set 호출은 현재 상태를 solver에 반영하는 지점을 보여줍니다. 실제 제어 구조에서 준비를 먼저 수행하고 최신 관측을 나중에 넣는 구상을 이해하는 데 이 구분이 유용합니다.
또한 두 단계는 같은 OCP solver 객체를 이어서 사용합니다. 준비와 피드백이 서로 다른 목적함수의 독립적인 최적화를 한 번씩 하는 구조로 읽기보다, 한 제어 입력을 구하기 위해 계산을 나눈 흐름으로 보아야 합니다.
이번 상태와 미래 예측은 서로 다른 자리에 있다
예제는 먼저 setup에서 모델, 비용, 제약, 예측 구간을 정의합니다. 비용 참조는 이 설정에서 만들어지고, 여기서 보는 반복문은 같은 문제 설정을 유지한 채 stage 0의 현재 상태를 바꿉니다. 따라서 코드가 구체적으로 보여주는 것은 정의된 문제 안에서 초기 상태가 새로 들어오는 경우입니다.
현재 상태를 넣는 위치와 미래 목표를 정의하는 위치를 구별하면 호출을 읽기 쉬워집니다. lbx·ubx를 같은 현재 값으로 설정하는 것은 시작점을 고정하는 연산입니다. 원하는 미래 궤적이나 비용 참조를 이 값으로 대신 지정하는 연산이 아닙니다.
준비 이후에 무엇을 바꾸는지도 이 문맥에서 판단해야 합니다. 이 예제는 준비 호출과 피드백 호출 사이에 비용이나 모델을 새로 만들지 않습니다. 그 두 단계 사이에서 명시적으로 갱신하는 것은 초기 상태입니다. 목표나 모델이 시간에 따라 바뀌는 다른 문제로 옮길 때는, 바뀐 정보가 어느 계산에 필요한지를 추가로 맞춰야 합니다. 현재 상태 업데이트 예제만으로 모든 문제 변경이 같은 위치에 들어간다고 읽을 수는 없습니다.
미래 구간을 계산하고 현재에 사용할 입력을 선택한다는 MPC의 기본 흐름은 휴머노이드 균형 제어의 MPC 설명과 연결됩니다. RTI의 단계 분리는 그 흐름 안에서 계산 시점을 배치하는 방법입니다.
첫 입력을 구한 뒤에는 시스템을 한 단계 전개한다
피드백 계산 다음의 get(0, 'u')는 첫 단계의 제어 입력을 가져옵니다. 예제는 그 값을 simU[i, :]에 저장하고, 이후 다음 호출로 시뮬레이션 상태를 전개합니다.
simX[i + 1, :] = integrator.simulate(
x=simX[i, :], u=simU[i, :]
)
여기서 integrator는 setup이 만든 AcadosSimSolver입니다. OCP solver가 제어 입력을 계산하고, 별도의 integrator가 현재 상태와 그 입력으로 시스템을 진행시킵니다. 다음 반복은 이렇게 얻은 simX[i+1]에서 이어집니다.
따라서 이 예제의 궤적 그림을 센서로 수집한 실제 로봇 궤적으로 읽으면 데이터의 성격이 달라집니다. 예측 문제를 푸는 객체와 시스템을 전개하는 객체가 분리돼 있다는 점이 코드에서 직접 확인되는 구조입니다.
파일에는 RTI뿐 아니라 SQP와 AS-RTI-A~D를 선택하는 분기도 있습니다. RTI 계열은 SQP_RTI를 사용하고, AS-RTI 선택에 따라 as_rti_level과 as_rti_iter를 설정합니다. 여기서 설명한 두 단계 호출 순서는 그 예제의 RTI 계열 경로입니다. 각 설정의 성능 차이를 알아보려면 같은 문제·실행 환경에서 얻은 결과를 따로 비교해야 합니다.
준비 시간과 피드백 시간은 한 쌍으로 읽는다
코드가 기록하는 t_preparation[i]와 t_feedback[i]는 각각의 solve() 직후 읽은 time_tot입니다. 반복문을 마친 뒤 두 배열에 1,000을 곱해 밀리초로 바꾸고, 각 배열의 최솟값·중앙값·최댓값을 따로 출력합니다.
이때 준비 단계의 최대 시간과 피드백 단계의 최대 시간이 같은 반복에서 나왔다는 뜻은 아닙니다. 다음은 가상 시간 기록입니다.
| 반복 | 준비 시간 | 피드백 시간 | 같은 반복의 두 시간 합 |
|---|---|---|---|
| 1 | 4ms | 1ms | 5ms |
| 2 | 2ms | 3ms | 5ms |
준비 시간의 최대는 4ms, 피드백 시간의 최대는 3ms입니다. 두 최댓값을 더한 7ms는 이 표에서 실제로 나타난 어느 반복의 합도 아닙니다. 각 반복의 계산량을 비교하려면 같은 인덱스의 두 값을 묶어야 합니다. 두 단계가 각기 어느 정도의 시간을 요구하는지와, 한 반복에서 합계가 얼마인지는 다른 질문입니다.
또 두 통계의 합과 전체 제어 루프의 소요 시간도 구분해야 합니다. 원본 코드에는 통계 기록 외에 상태 설정, 첫 입력 읽기, 잔차 확인, 시뮬레이션 호출 등이 이어집니다. 두 time_tot를 기록한 것만으로 그 바깥 작업까지 포함한 전체 루프의 시간을 측정한 셈이 되지는 않습니다.
RTI를 시간 관점에서 읽을 때는 총 계산량과 최신 상태 이후의 지연을 함께 봐야 합니다. 준비 계산을 앞쪽에 둘 수 있으면 최신 상태를 받은 뒤 남는 경로를 줄이는 데 의미가 있습니다. 이 Python 예제에서는 준비·상태 갱신·피드백·시뮬레이션을 차례로 호출합니다. 이런 순차 실행 안에서도 두 단계의 통계를 분리해 기록하므로, 계산이 어느 쪽에 배치되는지를 읽을 수 있습니다.
이 글은 2026년 10월 16일 확인한 acados 공식 기능 문서와 예제 소스를 해석했습니다. Solver 실행이나 제어 주기·계산 시간 측정은 수행하지 않았습니다.