Рецепт 10 — Добавить свой объект управления¶
Цель. Расширить TensorAeroSpace новым самолётом / ракетой / БПЛА: написать динамику, обернуть её в Gymnasium-среду по соглашениям библиотеки, зарегистрировать, линеаризовать для warm-start адаптивных агентов.
Связано. Рецепт 02 — контракт, которому должна удовлетворять ваша новая среда.
Короткая версия¶
Чтобы добавить объект, пишите два класса и один вызов регистрации:
- Функция динамики
f(x, u, t, params) -> xdot— чистый NumPy, без Gymnasium-зависимостей. - Класс Gymnasium-среды, обёртка над динамикой с интегратором, совместимый с контрактом
state_space/tracking_states/reference_signal. В репозитории нет общего базового класса — за образец возьмите одну из существующих сред: например,tensoraerospace/envs/b747.py(LinearLongitudinalB747/ImprovedB747Env) илиtensoraerospace/envs/f16/linear_longitudinal.py. gym.register('<NewPlant>-v0', entry_point='...')— чтобыgym.makeего нашёл.
1. Функция динамики¶
Начните с непрерывной ОДУ в форме пространства состояний. Пример — игрушечная продольная модель:
import numpy as np
def toy_plant_ode(x: np.ndarray, u: np.ndarray, t: float,
params: dict) -> np.ndarray:
"""xdot = f(x, u, t, params). Чистый NumPy, без побочных эффектов.
x = [alpha, q], u = [stab]
alpha_dot = -Z_alpha * alpha + q
q_dot = M_alpha * alpha + M_q * q + M_delta * stab
"""
alpha, q = float(x[0]), float(x[1])
stab = float(u[0])
Z_alpha = params['Z_alpha']
M_alpha = params['M_alpha']
M_q = params['M_q']
M_delta = params['M_delta']
return np.array([
-Z_alpha * alpha + q,
M_alpha * alpha + M_q * q + M_delta * stab,
], dtype=np.float64)
Держите params как обычный dict — он хорошо сериализуется в JSON для воспроизводимости и чекпойнтов.
2. Gymnasium-среда¶
Возьмите существующую среду как шаблон. Линейные F-16 и B747 — самые простые; нелинейная F-16 — правильный шаблон, если динамика нелинейная.
Класс среды обязан минимум выставлять:
class ToyPlantEnv(gym.Env):
def __init__(
self,
number_time_steps: int,
dt: float = 0.01,
initial_state: list[list[float]] = [[0.0], [0.0]],
reference_signal: np.ndarray | None = None,
state_space: list[str] = ['alpha', 'q'],
tracking_states: list[str] = ['alpha'],
output_space: list[str] | None = None,
use_reward: bool = True,
reward_func: Callable | None = None,
integrator: str = 'euler',
params: dict | None = None,
): ...
def reset(self, *, seed=None, options=None):
"""Вернуть (obs, info)."""
def step(self, action):
"""Вернуть (obs, reward, terminated, truncated, info)."""
def get_state(self):
"""Опциональный helper из примеров — текущее внутреннее состояние."""
Храните полное внутреннее состояние как self._x (форма (n_full_state,)) и срезайте его для наблюдения по state_space. Храните self.ref_signal = reference_signal, чтобы пользователи и агенты могли его читать.
Шаг интегрирования¶
Напишите небольшой _rk4_step или _euler_step, продвигающий self._x на один dt через вашу toy_plant_ode. Это единственное место, где имеет значение интегратор:
def _euler_step(self, u):
xdot = self.ode(self._x, u, self._t, self.params)
self._x = self._x + self.dt * xdot
self._t += self.dt
3. Регистрация¶
В конце файла среды или в tensoraerospace/envs/__init__.py:
from gymnasium.envs.registration import register
register(
id='ToyPlant-v0',
entry_point='my_package.envs:ToyPlantEnv',
max_episode_steps=10_000,
)
После этого:
import gymnasium as gym
import tensoraerospace # вызов register() внутри пакета станет доступен
env = gym.make('ToyPlant-v0', number_time_steps=2000, ...)
4. Warm-start адаптивных агентов¶
Как только среда работает с PID, вы можете сразу bootstrap'ить любого адаптивного агента (IHDP / iADP / AA-INDI) при условии линеаризации вокруг трима.
Найти трим¶
from scipy.optimize import fsolve
def trim_residual(z):
alpha, stab = z
return toy_plant_ode(np.array([alpha, 0.0]), np.array([stab]), 0.0, params)
alpha_trim, stab_trim = fsolve(trim_residual, x0=[0.0, 0.0])
Получить G_init через численный Jacobian или PE-возбуждение¶
Численно (одна строка):
eps = 1e-4
x0 = np.array([alpha_trim, 0.0])
u0 = np.array([stab_trim])
G_cont = (toy_plant_ode(x0, u0 + eps, 0, params) - toy_plant_ode(x0, u0, 0, params)) / eps
G_discrete = G_cont * dt # что нужно iADP / AA-INDI
Либо запустите 3-секундный мультисинус и подберите G из Δx / Δu (ноутбук iADP F-16 именно так и делает).
Подключить к агенту¶
См. Рецепт 06 — общий паттерн warm-start.
5. Тесты¶
Каждая новая среда должна иметь минимум:
- Round-trip тест —
env.reset()→ цикл со случайнымenv.step()→ траектория конечна, в пределахobservation_space. - Тест трима — из
(alpha_trim, 0)при действииstab_trimсостояние остаётся на месте (|x[t+1] - x[t]| < 1e-6). - Тест формы reference_signal — передача ссылочного сигнала неправильной формы даёт понятную ошибку.
Копируйте паттерны из tests/envs/ для существующих сред.
6. Публикация¶
Если ваша среда в отдельном pip-пакете — убедитесь, что register() выполняется при import. Самая чистая идиома:
Пользователи потом делают import my_package; gym.make('MyPlant-v0', ...) без церемоний.
Если среда — вклад в сам TensorAeroSpace, откройте PR с:
- функцией динамики в
tensoraerospace/aerospacemodel/<your_model>/, - классом среды в
tensoraerospace/envs/<your_model>/, - регистрацией в
tensoraerospace/envs/__init__.py, - 1–2 примерами-ноутбуками в
example/, - страницей
docs/{en,ru}/model/<your_model>.md, прописанной вmkdocs.yml.
Подводные камни¶
- Забытый
reshape(1, -1)у reference. Даже для одного канала среда ожидает 2-D. Если получитеIndexError: too many indices for array— проверьте это. - Несоответствие единиц модели и агента. Если динамика в радианах, а reference_signal в градусах (
output_rad=False), ошибка слежения взорвётся. Будьте явны в докстрингах. - Слишком большой шаг интегратора. Euler с
dt = 0.01нормален для ограниченной по полосе аэрокосмики; переключайтесь наrk4для агрессивной динамики привода (короткие постоянные времени) или уменьшайтеdtдо 0.005. - Недетерминированный
reset. Если вы рандомизируетеinitial_stateвreset(), принимайте и используйте аргументseedдля воспроизводимости.
Куда дальше¶
- Рецепт 02 — Устройство среды — контракт, которому должна удовлетворять новая среда.
- Рецепт 05 — Обучение deep-RL от и до — обучить RL-агента на своей среде.
- Рецепт 06 — Онлайн-адаптивные агенты — развернуть IHDP / iADP / AA-INDI на своей среде.