Devin.KR

모델 예측 제어 맛보기

개발자KR 조회 8

이 장에서 배우는 것

앞 장에서 다룬 경로 추종 제어는 로봇과 경로의 기하 관계를 이용해 조향 명령을 구했다. 이번에는 명령을 내리기 전에 로봇의 미래 움직임을 계산한다. 여러 명령을 가상으로 적용하고, 목적지에 얼마나 가까워지는지와 입력을 얼마나 사용하는지를 함께 비교한다. 그중 비용이 작은 명령을 선택하는 방법이 모델 예측 제어(Model Predictive Control, MPC)다.

이 장에서는 창고의 직선 통로를 따라 정차 지점으로 이동하는 로봇을 만든다. 연속적인 모든 입력을 최적화하는 대신, 미리 정한 속도와 각속도의 조합을 빠짐없이 조사한다. 작은 예제를 통해 예측, 평가, 선택, 재계산이라는 구조를 직접 확인하는 것이 목적이다.

  • 예측 구간이 시간과 계산량에 어떤 영향을 주는지 설명한다.
  • 목표 오차와 입력 사용량을 비용 함수로 표현한다.
  • 입력 후보의 모든 순서를 조사하는 격자 탐색형 제어기를 구현한다.
  • 계획한 입력 중 첫 번째만 실행하고 다음 상태에서 다시 계획한다.
  • 이산 후보, 단순한 운동 모델, 짧은 예측 구간의 한계를 구분한다.

문제 상황

창고 로봇이 물건을 내려놓는 위치로 이동한다고 하자. 로봇은 이미 직선 통로의 중심에 있으며, 정차 지점까지 남은 거리는 1.75m다. 일정 속도로만 움직이면 마지막 구간에서 목표를 지나칠 수 있다. 목표까지의 거리만 보고 속도를 정하더라도, 그 명령을 얼마 동안 유지하는지에 따라 결과가 달라진다.

여기서 필요한 질문은 “지금 어느 쪽으로 움직일까”에 그치지 않는다. “이 명령을 적용한 뒤에도 목표에 적절히 접근할 수 있을까”까지 생각해야 한다. 로봇이 낼 수 있는 명령을 몇 가지로 제한하면, 명령의 순서마다 미래 위치를 계산하고 비교할 수 있다. 예를 들어 빠른 전진을 세 번 할지, 빠른 전진 두 번 뒤 느린 전진을 할지 검토하는 방식이다.

예제에서는 로봇을 위치와 방향을 가진 점으로 나타낸다. 선속도와 각속도를 명령하면 즉시 그 값으로 움직인다고 가정한다. 가속도 제한이나 모터 응답 지연은 포함하지 않는다. 따라서 마지막 속도 변경은 실제 구동계의 감속 과정을 재현한 것이 아니라, 운동학 모델에서 선택한 명령의 변화다.

목표 방향은 지정하지 않고 목표 위치만 지정한다. 통로는 직사각형 허용 영역으로 표현한다. 선반 모양이나 복잡한 장애물은 넣지 않는다. 이처럼 문제를 좁히면 경로를 만드는 과정과 제어 입력을 고르는 과정을 섞지 않고, 예측 제어의 계산 구조를 살펴볼 수 있다.

예측 구간과 반복 계획

상태는 q = (x, y, θ), 입력은 u = (v, ω)로 둔다. x와 y는 위치, θ는 방향각, v는 선속도, ω는 각속도다. 시간 간격 Δt 동안 입력을 유지할 때 다음 상태를 아래 식으로 근사한다. 위치에는 구간 시작 시점의 방향각을 사용한다.

x_next = x + v * cos(theta) * dt
y_next = y + v * sin(theta) * dt
theta_next = theta + omega * dt

이 식은 전진 오일러 근사다. 회전하면서 움직이는 실제 곡선을 한 구간의 직선 이동과 방향 변경으로 표현한다. Δt가 커지거나 각속도가 커지면 예측 오차가 커질 수 있다. 이번 실습은 제어기와 시뮬레이터가 같은 식을 사용하므로 모델 불일치가 없다. 결과가 잘 맞는 이유에 이 가정도 포함된다는 점을 기억해야 한다.

예측 구간은 앞으로 계산할 단계 수 H와 시간 간격 Δt로 정한다. H가 3이고 Δt가 0.5초이면 현재부터 1.5초 뒤까지 살펴본다. 입력은 세 개지만, 상태는 현재 상태를 포함해 네 개다. 비용에는 명령을 적용한 뒤의 예측 상태 세 개를 사용한다.

현재 상태에서 선택한 입력 순서가 (u₀, u₁, u₂)라고 해도 세 입력을 모두 실행하지 않는다. 첫 입력 u₀만 0.5초 동안 적용하고, 새 상태를 받아 다시 세 단계의 계획을 세운다. 다음 계획의 출발점은 이전에 예상했던 상태가 아니라 그때 얻은 현재 상태다. 실물 로봇에서는 위치 추정 결과가 이 자리에 들어간다.

세 단계의 입력을 계획하더라도 첫 입력만 실행한 뒤 새 상태에서 다시 계획한다

예측 구간을 길게 하면 더 먼 결과를 평가할 수 있다. 그러나 길게 내다본다는 이유만으로 항상 나은 제어가 되는 것은 아니다. 모델의 오차가 누적될 수 있고, 이 장의 전수 탐색에서는 계산량도 빠르게 증가한다. 반대로 Δt만 줄이면 명령을 자주 바꿀 수 있지만, H가 같을 때 바라보는 미래 시간은 짧아진다.

예측 설정은 미래 시간과 탐색량을 함께 바꾼다
설정예측 시간입력 순서 수해석
H=3, Δt=0.5초1.5초729완성 코드의 설정
H=4, Δt=0.5초2.0초6,561한 단계 증가로 후보가 9배가 된다
H=3, Δt=0.25초0.75초729후보 수는 같지만 멀리 보지 못한다

표의 후보 수는 한 단계에 입력 조합이 아홉 개일 때의 값이다. 이 수는 허용 영역 위반으로 중간에 버리는 후보까지 포함한다. 예측 시간을 비교할 때는 H만 보지 말고 H와 Δt의 곱을 함께 봐야 한다.

비용 함수로 선택 기준 만들기

제어기는 “좋은 움직임”이라는 표현을 직접 계산할 수 없다. 이를 수치로 바꾼 것이 비용 함수다. 이번 예제에서는 각 단계의 목표 위치 오차, 입력 사용량, 마지막 예측 위치의 오차를 더한다. 모든 항은 작을수록 좋으며, 허용 영역을 벗어나는 입력 순서는 비교 대상에서 제외한다.

e_k_squared = (x_k - goal_x)**2 + (y_k - goal_y)**2

J = sum(
    e_k_squared + 0.05 * v_k**2 + 0.02 * omega_k**2
    for each predicted step
) + 4.0 * e_final_squared

여기서 각 단계의 입력 비용은 그 상태를 만들어 낸 입력에 대응한다. 코드에서는 입력을 하나 적용하고, 새 상태의 위치 오차와 방금 사용한 입력의 비용을 더한다. 마지막 상태도 단계 비용에 포함되며, 그 위치 오차에 종단 가중치 4.0을 곱한 값을 추가로 더한다. 따라서 마지막 위치 오차의 전체 계수는 5.0이다.

단계 위치 오차는 목표로 일찍 접근하도록 유도한다. 종단 비용은 예측 구간의 끝에서 어디에 도착하는지를 더 강조한다. 종단 비용만 있으면 마지막 위치가 같은 두 후보의 중간 움직임을 구별하기 어렵다. 반대로 단계 비용만 있으면 짧은 예측 구간의 마지막 상태를 충분히 중요하게 다루지 못할 수 있다.

입력 비용은 불필요한 이동과 회전을 줄이는 역할을 한다. 목표에 이미 도착했다면 정지 입력은 위치 오차와 입력 비용을 모두 0으로 만든다. 그러나 입력 사용량을 벌점으로 준다고 해서 속도 변화가 부드러워지는 것은 아니다. 연속한 두 명령의 차이는 이 비용 함수에 들어 있지 않다.

미터로 잰 위치 오차와 초당 미터로 잰 속도를 그대로 더하는 것은 물리량의 덧셈이라기보다 설계 점수의 구성이다. 가중치는 단위와 크기를 조정하는 역할도 한다. 좌표를 미터에서 센티미터로 바꾸면서 가중치를 유지하면 위치 오차 항이 10,000배 커져 선택 기준이 달라진다.

비용 항과 허용 조건은 서로 다른 역할을 맡는다
항목설정선택에 미치는 영향
단계 위치 오차제곱 거리 × 1.0예측 중에도 목표에 가까워지게 한다
선속도 비용v² × 0.05이동 입력의 사용량을 줄인다
각속도 비용ω² × 0.02회전 입력의 사용량을 줄인다
종단 위치 오차마지막 제곱 거리 × 4.0예측 끝의 위치를 더 강조한다
허용 영역x와 y의 범위 검사위반 후보를 선택 대상에서 제외한다

허용 조건과 큰 비용은 구별해야 한다. 통로 밖으로 나가는 후보에 큰 벌점만 주면 다른 비용이 더 클 때 그 후보가 선택될 여지가 있다. 이번 구현은 해당 후보의 평가를 즉시 중단한다. 이는 허용 영역 안에 있어야 한다는 조건을 비용 조정과 분리하기 위한 선택이다.

초기 상태에서 직진 입력만 사용하는 두 순서를 비교해 보자. 빠른 전진 세 번은 x가 0.50, 1.00, 1.50m가 된다. 느린 전진 뒤 빠른 전진 두 번은 0.25, 0.75, 1.25m가 된다. 두 번째 순서는 입력을 적게 사용하지만 목표 오차가 더 커서 총비용이 높다.

입력 사용량이 조금 작아도 목표 오차가 크면 총비용이 더 높아질 수 있다

이 비교는 전체 후보 중 두 개만 보여 준다. 실제 제어기는 회전이 포함된 순서까지 모두 평가한다. 예제의 초기 위치와 목표가 같은 직선 위에 있으므로 회전하지 않는 입력이 선택되지만, 코드에서 각속도를 미리 0으로 제한한 것은 아니다.

간단한 격자 탐색형 제어기

선속도 후보는 0.0, 0.5, 1.0m/s이고, 각속도 후보는 -0.5, 0.0, 0.5rad/s다. 두 목록의 모든 조합을 만들면 한 단계에 아홉 개의 입력이 생긴다. 여기서 격자는 공간 지도를 뜻하지 않는다. 선택 가능한 입력 값을 일정한 목록으로 나눈다는 뜻이다.

세 단계 입력 순서는 9³개다. 각 순서에 대해 현재 상태를 복사하고 세 번의 상태 전이를 계산한다. 허용 조건을 만족하면 비용을 비교하고, 지금까지 가장 작은 비용과 그 순서를 보관한다. 최종적으로는 최선 순서의 첫 입력만 반환한다.

이 방법은 주어진 이산 후보 안에서 최솟값을 찾는다. 후보 사이의 속도까지 포함한 연속적인 최적해를 구하지는 않는다. 후보를 촘촘하게 만들면 선택의 해상도는 좋아지지만 한 단계의 후보 수가 늘어난다. 단계별 후보 수가 M일 때 전체 순서 수는 M의 H제곱이고, 상태 전이 계산량은 대략 H × M의 H제곱에 비례한다.

같은 비용이 여러 번 나올 때의 규칙도 정한다. 코드에서는 비용이 엄격히 작을 때만 최선 후보를 바꾼다. 따라서 동점이면 목록에서 먼저 나온 순서를 유지한다. 후보 목록을 정렬되지 않은 자료구조로 만들거나 무작위로 섞으면 동점 처리 결과가 달라질 수 있다.

허용 가능한 순서가 하나도 없으면 예외를 발생시킨다. 실물 시스템에서는 별도의 정지 절차가 필요하지만, 이 실습에서 임의의 입력을 대신 반환하면 제어기가 계획에 실패했다는 사실을 놓치게 된다. 특히 현재 상태가 허용 영역 밖에 있다면 정지 입력을 준다고 곧바로 허용 상태가 되는 것은 아니다.

완성 코드

다음 내용을 mpc_intro.py로 저장한다. 외부 의존성은 numpy뿐이다. 무작위 표본은 사용하지 않으며, 확장 실습에서도 시작 조건을 유지할 수 있도록 시드를 7로 고정한다. 후보의 생성 순서와 동점 처리도 고정되어 있다.

import math
from itertools import product

import numpy as np


DT = 0.5
HORIZON = 3
GOAL = np.array([1.75, 0.0], dtype=float)
V_VALUES = (0.0, 0.5, 1.0)
W_VALUES = (-0.5, 0.0, 0.5)
CONTROLS = tuple(product(V_VALUES, W_VALUES))
SEQUENCES = tuple(product(CONTROLS, repeat=HORIZON))

V_WEIGHT = 0.05
W_WEIGHT = 0.02
TERMINAL_WEIGHT = 4.0


def motion(state, control):
    x, y, theta = state
    v, omega = control
    return np.array(
        [
            x + v * math.cos(theta) * DT,
            y + v * math.sin(theta) * DT,
            theta + omega * DT,
        ],
        dtype=float,
    )


def allowed(state):
    x, y, _ = state
    return (
        np.isfinite(state).all()
        and -0.25 <= x <= 2.25
        and -0.60 <= y <= 0.60
    )


def position_error(state):
    delta = state[:2] - GOAL
    return float(delta @ delta)


def evaluate(state, sequence):
    predicted = state.copy()
    total = 0.0

    for control in sequence:
        predicted = motion(predicted, control)
        if not allowed(predicted):
            return math.inf

        v, omega = control
        total += position_error(predicted)
        total += V_WEIGHT * v * v
        total += W_WEIGHT * omega * omega

    total += TERMINAL_WEIGHT * position_error(predicted)
    return total


def choose_control(state):
    if not allowed(state):
        raise ValueError("現在状態が許容領域外です")

    best_cost = math.inf
    best_sequence = None

    for sequence in SEQUENCES:
        cost = evaluate(state, sequence)
        if cost < best_cost:
            best_cost = cost
            best_sequence = sequence

    if best_sequence is None:
        raise RuntimeError("実行可能な入力列がありません")

    return best_sequence[0], best_cost


def main():
    np.random.seed(7)
    state = np.array([0.0, 0.0, 0.0], dtype=float)

    print(f"후보 수: {len(SEQUENCES)}")
    print("회차, x, y, v, omega, 예측 비용")

    for step in range(1, 6):
        control, cost = choose_control(state)
        state = motion(state, control)
        v, omega = control
        print(
            f"{step}, {state[0]:.2f}, {state[1]:.2f}, "
            f"{v:.2f}, {omega:.2f}, {cost:.4f}"
        )

    distance = math.sqrt(position_error(state))
    print(f"최종 거리 오차: {distance:.3f} m")


if __name__ == "__main__":
    main()

줄별 해설

import math는 삼각함수와 무한대 값을 사용하기 위한 줄이다. product는 후보 목록의 모든 조합을 생성한다. numpy는 상태 벡터를 저장하고 위치 오차의 내적을 계산하는 데 사용한다. 전문 최적화 라이브러리를 사용하지 않아도 탐색 과정 전체를 읽을 수 있다.

DT와 HORIZON은 각각 한 입력을 유지하는 시간과 예측 단계 수다. GOAL에는 위치 두 성분만 있다. 상태에는 방향각까지 세 성분이 있지만 비용에서는 state[:2]만 사용하므로 목표 방향은 평가하지 않는다.

CONTROLS를 만드는 줄은 선속도와 각속도의 조합 아홉 개를 만든다. SEQUENCES는 이 입력을 세 번 고르는 모든 순서를 만든다. 튜플에 저장했기 때문에 매 제어 주기마다 다시 순회할 수 있다. 한 번만 소비할 수 있는 반복자를 전역에 그대로 보관하면 다음 주기에 탐색할 후보가 남지 않는 문제가 생긴다.

motion의 첫 두 줄은 상태와 입력을 성분으로 나눈다. 반환 배열의 첫째와 둘째 항은 구간 시작 방향으로 이동한 위치이고, 셋째 항은 각속도를 적용한 방향이다. 입력 상태를 직접 수정하지 않고 새 배열을 반환하므로 한 후보의 예측이 다른 후보의 시작 상태를 바꾸지 않는다.

allowed는 숫자가 유한한지 먼저 확인하고 좌표 범위를 검사한다. 이 예제의 허용 영역은 직사각형이며, 오일러 모델의 두 예측 위치 사이를 직선으로 해석하면 두 끝점이 안에 있을 때 그 선분도 안에 있다. 다만 실제 회전 궤적이나 로봇 몸체까지 검사한다는 뜻은 아니다.

position_error에서 목표를 빼면 위치 오차 벡터가 된다. delta @ delta는 x 오차의 제곱과 y 오차의 제곱을 더한다. 비용 비교에는 제곱근이 필요하지 않다. 마지막 출력에서만 제곱근을 취해 거리를 미터 단위로 표시한다.

evaluate는 현재 상태의 복사본과 비용 0에서 시작한다. 반복문 안에서 먼저 입력을 적용하고, 예측 상태가 허용되는지 검사한다. 허용되지 않으면 무한대를 반환하므로 그 후보는 유한한 비용을 가진 후보보다 선택 우선순위가 낮다.

이어서 위치 오차, 선속도 비용, 각속도 비용을 각각 더한다. 마지막 예측 상태에 대한 종단 비용은 반복문 밖에서 한 번만 더한다. 반복문 안에 종단 비용을 넣으면 모든 단계의 위치 오차에 추가 가중치를 주게 되어 다른 목적 함수를 계산한다.

choose_control은 현재 상태의 유효성을 확인한 뒤 최선 비용을 무한대로 초기화한다. 비교문이 cost < best_cost이므로 같은 비용에서는 먼저 찾은 후보를 보존한다. 모든 후보가 무한대이면 최선 순서가 끝까지 비어 있어 예외가 발생한다.

반환문의 best_sequence[0]이 반복 계획의 핵심이다. 함수 내부에서는 세 입력을 평가했지만 호출자에게 넘기는 실행 입력은 하나다. 전체 순서를 반환해 한꺼번에 적용하면, 새 상태에 맞춰 남은 계획을 고칠 기회를 잃는다.

main의 반복문은 현재 상태로 입력을 고르고, 그 입력을 한 번 적용하고, 결과를 출력한다. 행에 표시되는 좌표는 실행 후 좌표이고, 예측 비용은 실행 전 상태에서 계획할 때 계산한 값이다. 비용은 그 한 번의 실행 비용이 아니라 선택한 세 단계 계획 전체의 점수다.

실행 결과

numpy가 준비된 macOS 또는 Linux 환경에서 다음 명령으로 문법과 경고를 검사한 뒤 실행한다. 첫 명령은 정상적으로 끝나면 출력이 없다. 두 번째 명령의 예상 출력은 아래와 같다.

python3 -W error -m py_compile mpc_intro.py
python3 -W error mpc_intro.py
후보 수: 729
회차, x, y, v, omega, 예측 비용
1, 0.50, 0.00, 1.00, 0.00, 2.5875
2, 1.00, 0.00, 1.00, 0.00, 0.7375
3, 1.50, 0.00, 1.00, 0.00, 0.1250
4, 1.75, 0.00, 0.50, 0.00, 0.0125
5, 1.75, 0.00, 0.00, 0.00, 0.0000
최종 거리 오차: 0.000 m

첫 회차의 최선 계획은 선속도 1.0을 세 번 사용하는 직진이다. 위치 오차 제곱의 합은 1.25² + 0.75² + 0.25² = 2.1875다. 입력 비용은 0.15이고, 종단 비용은 4 × 0.25² = 0.25다. 이를 더하면 출력된 2.5875가 된다.

두 번째 회차에서는 목표가 더 가까워져 선속도 순서 1.0, 1.0, 0.5를 선택한다. 하지만 여기에서도 첫 입력만 사용한다. 네 번째 회차에서 실제 명령이 0.5가 되어 남은 0.25m를 이동한다. 다섯 번째 회차는 목표에서 정지 입력을 선택하는지 확인하기 위해 추가했다.

거리 오차가 0으로 끝나는 데에는 목표 위치와 후보 간격이 잘 맞는다는 조건이 있다. 한 번의 이동 거리가 0.25m의 배수이고 목표도 그 격자 위에 있다. 목표를 다른 위치로 바꾸면 잔여 오차가 생길 수 있다. 이 결과 하나를 보고 임의의 목표에서 정확히 정지한다고 일반화해서는 안 된다.

출력의 비용은 매번 새 상태에서 계산한 유한 구간의 비용이다. 이번에는 감소하지만, 모든 문제에서 반드시 감소한다는 보장은 없다. 외란이나 목표 변경이 있으면 비용이 증가할 수 있다. 종단 비용을 넣었다는 사실만으로 안정성이나 이후의 실행 가능성이 증명되는 것도 아니다.

실무에서 자주 틀리는 것

계획한 입력을 모두 실행한다

다음 코드는 처음 세운 계획을 끝까지 실행한다. 예측 중에는 유효했던 상태라도 실행 중의 오차로 달라질 수 있다. 예측 구간과 실제로 피드백 없이 실행하는 구간을 같은 것으로 취급한 실수다.

# 틀린 구조: 계획 전체를 연속 실행한다.
for control in best_sequence:
    state = motion(state, control)

첫 입력만 실행하고 새 상태에서 선택 함수를 다시 호출한다. 실물 장치에서는 아래의 상태 전이 계산을 센서에 기반한 상태 갱신과 연결해야 한다.

# 고친 구조: 한 입력을 실행할 때마다 다시 계획한다.
for _ in range(5):
    control, cost = choose_control(state)
    state = motion(state, control)

이 차이는 단순한 반복문 배치 문제가 아니다. 미래 예측을 참고하되 실제 실행 뒤에 얻은 정보를 계속 반영할 수 있는지를 결정한다.

후보 평가 중 현재 상태를 덮어쓴다

각 후보는 동일한 현재 상태에서 출발해야 한다. 다음처럼 현재 상태를 누적해서 갱신하면 뒤쪽 후보는 앞쪽 후보의 도착점에서 시작한다. 이때 비용 비교는 더 이상 공정한 비교가 아니다.

# 틀린 코드
for sequence in SEQUENCES:
    for control in sequence:
        state = motion(state, control)
    cost = position_error(state)

상태 복사는 후보 하나를 평가하는 함수 안에서 수행한다. 또한 단계 비용과 종단 비용까지 모두 포함한 값을 비교해야 한다.

# 고친 코드
for sequence in SEQUENCES:
    cost = evaluate(state, sequence)

완성 코드의 evaluate는 시작할 때 상태를 복사하고, motion도 새 배열을 반환한다. 나중에 성능을 개선하면서 배열을 제자리 수정하도록 바꾼다면 이 독립성이 유지되는지 다시 확인해야 한다.

종단 오차를 현재 위치에서 계산한다

종단 비용은 입력 순서가 만든 마지막 상태를 평가해야 한다. 현재 상태의 오차를 더하면 모든 후보에 같은 값이 붙으므로 선택에 영향을 주지 않는다.

# 틀린 코드: evaluate의 마지막 부분
total += TERMINAL_WEIGHT * position_error(state)

반복문이 끝났을 때 남아 있는 마지막 예측 상태를 사용한다.

# 고친 코드
total += TERMINAL_WEIGHT * position_error(predicted)

현재 오차를 잘못 사용해도 프로그램은 실행되고 비용도 출력된다. 오류를 찾을 때는 실행 여부만 보지 말고, 서로 다른 입력 순서에 대해 종단 항이 달라지는지 확인해야 한다.

입력 사용량과 입력 변화량을 혼동한다

속도의 제곱을 벌점으로 주는 것은 큰 속도를 줄이는 기준이다. 이를 속도 변화 억제라고 설명하면 목적과 구현이 어긋난다.

# 틀린 해석: 이 항만으로 속도 변화가 억제된다고 생각한다.
total += 0.05 * v * v

변화를 줄이고 싶다면 이전 입력과의 차이를 별도 항으로 넣는다. 다음 조각은 기존 입력 비용을 유지하면서 변화 비용을 추가하는 구조다. last_control은 직전에 실제로 실행한 입력이며, 후보마다 같은 값에서 시작해야 한다.

previous_v, previous_w = last_control
for control in sequence:
    v, omega = control
    total += 0.05 * v * v + 0.02 * omega * omega
    total += 0.10 * (v - previous_v) ** 2
    total += 0.10 * (omega - previous_w) ** 2
    previous_v, previous_w = control

이 항을 추가하려면 evaluate와 choose_control에 이전 입력을 전달하고 실행 후에 갱신해야 한다. 위 조각은 위치 비용과 상태 전이 부분을 생략한 수정 예이며, 완성 코드의 예상 출력에는 적용하지 않았다. 변화 비용은 변화를 억제하는 선호이고, 가속도 상한을 강제하는 조건과는 다르다.

한눈에 보기

완성 코드의 각 부분은 예측 제어의 한 역할에 대응한다
구성 요소코드 위치의미와 확인점
운동 모델motion명령을 적용한 다음 상태를 계산한다
예측 구간DT, HORIZON0.5초 간격으로 세 단계 앞을 본다
후보 집합CONTROLS, SEQUENCES아홉 입력으로 만든 729개 순서를 조사한다
허용 조건allowed영역 밖의 예측 상태가 있으면 후보를 버린다
비용 평가evaluate단계 오차, 입력 비용, 종단 오차를 더한다
입력 선택choose_control최선 순서 중 첫 입력만 반환한다
반복 계획main의 반복문실행 후의 상태를 다음 계획에 사용한다

이 구현의 장점은 어떤 후보가 왜 선택되는지 직접 계산할 수 있다는 데 있다. 대신 후보 수와 예측 단계가 늘어나면 전수 탐색이 부담스러워진다. 실제 적용에서는 계산 시간뿐 아니라 상태 추정 오차, 구동계의 응답, 몸체 크기와 경계 여유도 함께 모델링해야 한다.

이번 실습에서는 한 로봇이 고정된 목표를 향해 움직였다. 다음 장에서는 같은 공간을 사용하는 다른 로봇까지 고려하면서 이동과 작업을 어떻게 조율할지 다룬다.

연습 문제

  1. H를 4로 바꾸면 후보 순서 수와 예측 시간은 각각 얼마인가. Δt를 0.25초로 줄이면서 예측 시간을 1.5초로 유지하면 H와 후보 순서 수가 얼마인지도 구하라.
  2. 상태가 (1.50, 0.00, 0.00)일 때, 선속도 순서 (0.5, 0.0, 0.0)과 (0.0, 0.5, 0.0)의 비용을 계산하라. 모든 각속도는 0이다.
  3. 목표를 (1.80, 0.00)으로 바꾸고 직진 입력만 허용한다고 하자. 초기 x가 0일 때 목표에 정확히 도착할 수 있는지 이동 거리의 후보로 설명하라. 위치 해상도를 높이는 수정도 하나 제안하라.
  4. 변화 비용을 추가하기 위해 이전 실제 입력을 제어기에 전달하려 한다. 후보 평가 중 이전 입력을 공유 변수로 계속 갱신하면 어떤 문제가 생기는가. 올바른 초기화 위치와 실행 후 갱신 위치를 설명하라.

정답과 해설

  1. H가 4이면 후보는 9⁴ = 6,561개이고 예측 시간은 4 × 0.5 = 2.0초다. Δt가 0.25초일 때 1.5초를 보려면 H가 6이어야 하며, 후보는 9⁶ = 531,441개다. 시간 간격을 절반으로 줄이면서 같은 미래 시간을 유지하면 이 탐색 방식에서는 계산 부담이 크게 증가한다. 상태 전이 횟수까지 생각하면 후보 수 외에 단계 수 증가도 고려해야 한다.

  2. 첫 순서는 첫 단계에 목표에 도착하므로 세 단계의 위치 오차와 종단 오차가 모두 0이다. 선속도 비용만 남아 0.05 × 0.5² = 0.0125다. 두 번째 순서는 첫 단계에 0.25m의 오차가 남아 0.0625가 추가된다. 나머지 항은 같으므로 총비용은 0.0750이다. 단계 위치 비용이 더 일찍 도착하는 순서를 선호하게 만든다.

  3. 직진 한 번의 이동 거리는 0, 0.25, 0.50m이므로 초기 위치가 0이면 도달 위치는 0.25m의 배수다. 1.80m는 이 집합에 속하지 않아 정확히 도착할 수 없다. 예를 들어 선속도 후보에 0.1m/s를 추가하면 0.05m 이동이 가능해져 1.75m에서 1.80m로 갈 수 있다. 다만 도달 가능한 위치가 된다는 사실과 주어진 비용에서 그 입력이 선택된다는 사실은 구분해야 한다.

  4. 이전 입력을 후보 사이에 공유하며 바꾸면 뒤쪽 후보의 첫 변화 비용이 앞쪽 후보의 마지막 입력을 기준으로 계산된다. 모든 후보는 직전에 실제 실행한 동일한 입력에서 시작해야 한다. 따라서 각 evaluate 호출 안에서 지역 변수 previous_v와 previous_w를 초기화한다. 실제 이전 입력인 last_control은 최선 입력을 한 번 실행한 뒤에만 갱신한다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.