Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Моделирование Cartpole и реализация на Python: управление PID, LQR и MPC

Задача CartPole — удержать стержень вертикально (угол 0) и тележку в центре направляющей (положение 0).

В руководстве не приводится вывод формул. Если хотите разобраться в основах PID, LQR и MPC, изучите их отдельно.

I. Среда и предварительные знания

  1. Windows
  2. Матричная теория и теория управления
  3. Установка окружения — см. requirements.txt

II. Моделирование Cartpole

Ниже приведён фрагмент официальной динамической модели Cartpole, см. http://incompleteideas.net/sutton/book/code/pole.c

Подробности — в файле cartpole_env.py из папки code. Во время практики, чтобы применить линейные регуляторы, мы сделали аппроксимацию динамической модели; её описание дано ниже.

# temp = (force + self.polemass_length * theta_dot * theta_dot * sintheta) / self.total_mass
# thetaacc = (self.gravity * sintheta - costheta * temp) / (self.length * (4.0 / 3.0 - self.masspole * costheta * costheta / self.total_mass))
# xacc = temp - self.polemass_length * thetaacc * costheta / self.total_mass

В виде формул это выглядит так:

Для линейного регулятора угол маятника мал, поэтому его можно аппроксимировать: , , . После такой аппроксимации уравнения принимают вид:

Запишем систему в пространстве состояний:

Пространство состояний используется в коде LQR и MPC. Соответствующий код на Python:

A = np.eye(4) + del_t * np.array([[0, 1, 0, 0],
                [0, 0, (-3*m*g)/(m+4*M), 0],
                [0, 0, 0, 1],
                [0, 0, (3*(M+m)*g)/(L*(m+4*M)), 0]])
B = del_t * np.array([[0],
                [(1/(M+m))+((3*m)/(m+4*M))],
                [0],
                [-(3)/(L*(m+4*M))]])

III. Разбор кода

1. Регулятор PID

В коде используется только PD-регулирование: отдельно вычисляются отклонения положения тележки и угла стержня, и по ним выполняется PD-регулирование. Ключевой код PD-регулятора:

def pid_cart(self, position):
    bias = position
    d_bias = bias - self.bias_cart_1
    balance = self.kp_cart * bias + self.kd_cart * d_bias
    self.bias_cart_1 = bias
    return balance

def pid_pole(self, angle):
    bias = angle
    d_bias = bias - self.bias_pole_1
    balance = -self.kp_pole * bias - self.kd_pole * d_bias
    self.bias_pole_1 = bias
    return balance

2. Регулятор LQR

Постановка задачи дискретного LQR:

Этапы вычисления дискретного LQR:

  1. Решить уравнение для матрицы :

  1. Вычислить матрицу обратной связи :

  1. Вычислить управляющее воздействие :

В практическом использовании LQR для Cartpole — весовая матрица перекрёстных членов состояния и управления. В задаче Cartpole перекрёстный член не нужно штрафовать, поэтому полагают нулевой матрицей.

Ключевой код LQR-регулятора:

def control_output(self, state_des, state_now):
    S = np.matrix(linalg.solve_discrete_are(self.A, self.B, self.Q, self.R))
    K = np.matrix(linalg.inv(self.B.T * S * self.B + self.R) * (self.B.T * S * self.A))
    # print("state_now", state_now)
    # print("state_des", state_des)
    action_list  = -10*np.dot(K, state_now - state_des)
    action = action_list[0,0]
    return action

Уравнение Риккати решается функцией solve_discrete_are из модуля linalg.

3. Регулятор MPC

Основные компоненты MPC:

  1. Прогнозная модель: по текущему состоянию и будущим управляющим воздействиям предсказывает траекторию состояния системы.
  2. Оптимизация на скользящем горизонте: ищет последовательность управляющих воздействий, при которой критерий качества (функция стоимости) оптимален на горизонте прогноза.
  3. Коррекция по обратной связи: корректирует прогноз модели по измерениям и на следующем шаге заново прогнозирует и оптимизирует по новому состоянию.

Постановка линейной задачи MPC:

Здесь:

  • — горизонт прогноза (длина предсказания).
  • — прогноз состояния в момент , сделанный в момент .
  • — управляющее воздействие в момент , запланированное в момент .
  • — весовая матрица состояния.
  • — весовая матрица управляющего воздействия.
  • — весовая матрица терминального состояния.

Ключевой код MPC-регулятора:

  1. Построение матриц задачи QP: вычисляются матрицы и из формулировки QP.
def get_QPMatrix(A, B, Q, R, F, N):
    M = np.vstack([np.eye(n), np.zeros((N*n, n))])
    C = np.zeros(((N+1)*n, N*p))
    temp = np.eye(n)
    for i in range(1,N+1):
        rows = i * n + np.arange(n)
        C[rows,:] = np.hstack([temp @ B, C[rows-n, :-p]])
        temp = A @ temp
        M[rows,:] = temp

    Q_ = np.kron(np.eye(N), Q)
    rows_Q, cols_Q = Q_.shape
    rows_F, cols_F = F.shape
    Q_bar = np.zeros((rows_Q+rows_F, cols_Q+cols_F))
    Q_bar[:rows_Q, :cols_Q] = Q_
    Q_bar[rows_Q:, cols_Q:] = F
    R_bar = np.kron(np.eye(N), R)

    # G = M.T @ Q_bar @ M
    E = C.T @ Q_bar @ M
    H = C.T @ Q_bar @ C + R_bar
    return E, H
  1. Решение MPC в реальном времени:

CasADi — мощный фреймворк символьных вычислений и автоматического дифференцирования, который часто применяют для задач оптимального управления и нелинейной оптимизации.

def mpc_prediction(x_k, E, H, N, p):
    # Определяем переменные оптимизации
    U = ca.SX.sym('U', N * p)
    # Определяем целевую функцию
    objective = 0.5 * ca.mtimes([U.T, H, U]) + ca.mtimes([U.T, E, x_k])
    qp = {'x': U, 'f': objective}
    opts = {'print_time': False, 'ipopt': {'print_level': 0}}
    solver = ca.nlpsol('solver', 'ipopt', qp, opts)

    # Решаем задачу
    sol = solver()
    # Извлекаем оптимальное решение
    U_k = sol['x'].full().flatten()
    u_k = 10*U_k[:p]  # берём первый элемент результата

    return u_k

IV. Результаты кода

1. Результаты PID-управления

2. Результаты LQR-управления

3. Результаты MPC-управления