Документация Engee
Notebook

Сравнение PD и PID-регуляторов с антивиндапом в задаче управления дистанцией в колонне.

Управление движением колонн является одной из ключевых задач в области интеллектуальных транспортных систем. Основная цель – обеспечить синхронное движение группы транспортных средств с заданной дистанцией между соседними автомобилями, минимизируя рывки, исключая обгоны внутри колонны и гарантируя безопасность. Для достижения этой цели каждый ведомый автомобиль оснащается регулятором, который на основе информации о положении и скорости впереди идущего транспортного средства вычисляет требуемое ускорение. Однако реальные условия – ограничения по ускорению и скорости, задержки в каналах связи, шумы измерений – существенно усложняют задачу. В данной работе представлены две модели системы управления колонной, реализованные в среде имитационного моделирования Engee: базовая модель на основе классического ПД-регулятора и улучшенная модель, использующая ПИД-регулятор. В этом примере проводится сравнительный анализ их эффективности по таким критериям, как точность поддержания дистанции, наличие обгона и устойчивость к возмущениям.

Простая модель (PD‑регулятор)

В базовой версии используется классический ПД-регулятор:

,

где ( ) – ошибка дистанции,

( D ) – заданная дистанция,

( ) – ошибка скорости.


Данная модель демонстрирует вариант параллельного построения, при котором ведомые стремятся занять заданные позиции относительно лидера. В процессе симуляции наблюдается, что пары «Ведомый‑1 и Ведомый‑3», а также «Ведомый‑2 и Ведомый‑4» выстраиваются синхронно — каждая пара оказывается на одинаковом удалении от лидера. Благодаря различным начальным позициям можно наглядно увидеть, как ведомые по‑разному разгоняются, чтобы достичь требуемой дистанции.

Однако в текущей реализации присутствуют следующие ограничения:

  • При наличии постоянных внешних возмущений (например, уклона дороги или бокового ветра) в системе возникает установившаяся ошибка по дистанции – ведомые не могут точно поддерживать заданное расстояние.

  • Зона нечувствительности обрезает малые сигналы управления. В сочетании с отсутствием интегральной составляющей в регуляторе это приводит к долговременному дрейфу позиции, а в итоге – к обгону ведомыми лидера. Таким образом, чисто ПД‑регулятор не обеспечивает устойчивого сопровождения дистанции на длительных интервалах времени.

Структура ведомого включает вычисление ошибок, усиление сигналов, сумматор, блок ограничения ускорения и двойной интегратор (скорость → позиция).

image.png

Все ведомые имеют идентичную архитектуру и подключены параллельно: информация от лидера поступает непосредственно на каждый ведомый, что приводит к формированию пар «Ведомый‑1 и Ведомый‑3», «Ведомый‑2 и Ведомый‑4», движущихся синхронно.

image.png
In [ ]:
function run_model(name)
    p, is_loaded = joinpath(@__DIR__, name * ".engee"), name  [m.name for m in engee.get_all_models()]
    is_loaded && engee.load(p, force=true)
    out = engee.run(engee.open(name), verbose=true)
    is_loaded && engee.close(name, force=true)
    sleep(0.1); return out
end

initial_positions = [-10.0, -20.0, -30.0, -40.0] # Начальные позиции ведомых относительно лидера
accel_limit_lower = -3.0   # мин. ускорение, м/с²
accel_limit_upper =  3.0   # макс. ускорение, м/с²
speed_limit_lower = 0.0    # мин. скорость, м/с
speed_limit_upper = 15.0   # макс. скорость, м/с

# Параметры ПД-регулятора
D = 10.0          # заданная дистанция, м
Kp = 0.5          # пропорциональный коэффициент
Kd = 2.0          # дифференциальный коэффициент

run_model("Platooning_Basic")
Building...
Progress 0%
Progress 100%
Progress 100%
Out[0]:
SimulationResult(
    run_id => 12,
    "Ведомый-4.x" => WorkspaceArray{Float64}("Platooning_Basic/Ведомый-4.x")
,
    "Ведомый-1.x" => WorkspaceArray{Float64}("Platooning_Basic/Ведомый-1.x")
,
    "Ведомый-2.v" => WorkspaceArray{Float64}("Platooning_Basic/Ведомый-2.v")
,
    "Ведомый-3.v" => WorkspaceArray{Float64}("Platooning_Basic/Ведомый-3.v")
,
    "Лидер.X_0" => WorkspaceArray{Float64}("Platooning_Basic/Лидер.X_0")
,
    "Ведомый-1.v" => WorkspaceArray{Float64}("Platooning_Basic/Ведомый-1.v")
,
    "Ведомый-3.x" => WorkspaceArray{Float64}("Platooning_Basic/Ведомый-3.x")
,
    "Лидер.V_0" => WorkspaceArray{Float64}("Platooning_Basic/Лидер.V_0")
,
    "Ведомый-2.x" => WorkspaceArray{Float64}("Platooning_Basic/Ведомый-2.x")
,
    "Ведомый-4.v" => WorkspaceArray{Float64}("Platooning_Basic/Ведомый-4.v")

)
In [ ]:
# Извлечение данных с прореживанием
function get_signal(simout, key, step=1)
    t = collect(simout[key]).time
    val = collect(simout[key]).value
    return t[1:step:end], val[1:step:end]
end
t_lx, x_l = get_signal(simout, "Platooning_Basic/Лидер.X_0", 5)
t_lv, v_l = get_signal(simout, "Platooning_Basic/Лидер.V_0", 5)
t1x, x1 = get_signal(simout, "Platooning_Basic/Ведомый-1.x", 5)
t1v, v1 = get_signal(simout, "Platooning_Basic/Ведомый-1.v", 5)
t2x, x2 = get_signal(simout, "Platooning_Basic/Ведомый-2.x", 5)
t2v, v2 = get_signal(simout, "Platooning_Basic/Ведомый-2.v", 5)
t3x, x3 = get_signal(simout, "Platooning_Basic/Ведомый-3.x", 5)
t3v, v3 = get_signal(simout, "Platooning_Basic/Ведомый-3.v", 5)
t4x, x4 = get_signal(simout, "Platooning_Basic/Ведомый-4.x", 5)
t4v, v4 = get_signal(simout, "Platooning_Basic/Ведомый-4.v", 5)

# График скоростей
plot(title="Скорости", xlabel="Время (с)", ylabel="Скорость (м/с)")
plot!(t_lv, v_l, label="Лидер")
plot!(t1v, v1, label="Ведомый-1")
plot!(t2v, v2, label="Ведомый-2")
plot!(t3v, v3, label="Ведомый-3")
display(plot!(t4v, v4, label="Ведомый-4"))

# График позиций
plot(title="Позиции", xlabel="Время (с)", ylabel="Позиция (м)")
plot!(t_lx, x_l, label="Лидер")
plot!(t1x, x1, label="Ведомый-1")
plot!(t2x, x2, label="Ведомый-2")
plot!(t3x, x3, label="Ведомый-3")
plot!(t4x, x4, label="Ведомый-4")
Out[0]:

Как мы видим ведомые опережают друг друга, чего быть не должно.

Улучшенная модель (ПИД‑регулятор с anti‑windup)

Для устранения указанных проблем была разработана усложнённая версия, в которой реализован ПИД-регулятор с дополнительными механизмами:

Внесённые улучшения:

  1. Добавлен интегральный канал – обеспечивает нулевую установившуюся ошибку при постоянных возмущениях.
  2. Ограничение интегратора (anti‑windup) – выход интегратора ошибки насыщается в пределах ([-0.3; 0.3]), что предотвращает его неограниченное накопление при длительном нахождении сигнала управления в зоне нечувствительности или в насыщении по ускорению/скорости. Anti-windup (анти-виндаун) — это набор методов и техник, направленных на предотвращение или смягчение эффекта «виндауна» (windup) в системах управления, особенно в PID-контроллерах, когда управляющий сигнал превышает возможности актуатора (например, достигает предела мощности или скорости). Этот эффект может привести к накоплению ошибок, нестабильности системы и ухудшению её производительности.
    image.png
  3. Используется ограничение ускорения.
  4. Перенастройка коэффициентов – для улучшения переходных процессов увеличен дифференциальный коэффициент (K_d = 2.0), а для замедления накопления интеграла выбран относительно небольшой интегральный коэффициент (K_i = 0.05). Пропорциональный коэффициент оставлен прежним (K_p = 0.5).
image.png

В статье демонстрируется эволюция модели: от простого ПД-регулятора к полноценному ПИД-регулятору с защитой от виндапа. Показано, что именно добавление интегральной составляющей и механизма anti‑windup позволяет полностью устранить эффект обгона лидера, при должной настройке можно ликвидировать обгон впереди идущего, обеспечить устойчивое удержание дистанции и реалистичное поведение колонны на длительных интервалах моделирования.

Результаты работы могут быть полезны при проектировании систем адаптивного круиз-контроля (ACC) и управления беспилотными колоннами.

image.png
In [ ]:
leader_speed_max = 10.0          # макс. скорость лидера, м/с
leader_accel_max = 3.0           # ускорение лидера, м/с²
D = 10.0                         # заданная дистанция, м
Kp, Kd, Ki = 0.5, 2.0, 0.05      # коэффициенты ПИД-регулятора
accel_limit_lower, accel_limit_upper = -2.0, 2.0   # предел ускорения, м/с²
speed_limit_lower, speed_limit_upper = 0.0, 15.0   # предел скорости, м/с
int_err_lower, int_err_upper = -0.3, 0.3           # анти-виндап интегратора

# Шумы измерений
noise_mean = 0.0
noise_variance_position = 0.01
noise_variance_speed = 5
noise_sample_time = 0.01

delay_time = 0.3 # транспортная задержка, с
initial_positions = [-20.0, -40.0, -60.0] # Начальные позиции ведомых (от лидера)

run_model("Platooning_AntiWindup")

t_lx, x_l = get_signal(simout, "Platooning_AntiWindup/Лидер.X_0")
t_lv, v_l = get_signal(simout, "Platooning_AntiWindup/Лидер.V_0")
t1x, x1 = get_signal(simout, "Platooning_AntiWindup/Ведомый-1.x")
t1v, v1 = get_signal(simout, "Platooning_AntiWindup/Ведомый-1.v")
t2x, x2 = get_signal(simout, "Platooning_AntiWindup/Ведомый-2.x")
t2v, v2 = get_signal(simout, "Platooning_AntiWindup/Ведомый-2.v")
t3x, x3 = get_signal(simout, "Platooning_AntiWindup/Ведомый-3.x")
t3v, v3 = get_signal(simout, "Platooning_AntiWindup/Ведомый-3.v")
t = t_lx # Общая временная шкала (используем время лидера)
n = length(t);
Building...
Progress 0%
Progress 100%
Progress 100%

Данный график демонстрирует динамику относительного положения ведомых относительно лидера во времени, где отрицательные значения соответствуют отставанию, нулевая линия – позиции лидера, а отсутствие пересечения этой линии подтверждает отсутствие обгона.

In [ ]:
t = t_lx
plot(t, [x1 .- x_l, x2 .- x_l, x3 .- x_l], 
     label=["Вед-1" "Вед-2" "Вед-3"], 
     xlabel="Время (с)", ylabel="Относительная позиция (м)")
hline!([0], linestyle=:dash, label="Позиция лидера")
Out[0]:

Анимация фазового портрета иллюстрирует эволюцию траекторий всех автомобилей в координатах «позиция – скорость», где отсутствие пересечения траекторий ведомых с траекторией лидера подтверждает безопасный режим движения без обгонов.

In [ ]:
@gif for i in 1:n fps = 5
    plot(title="Фазовый портрет колонны",
         xlabel="Позиция, м", ylabel="Скорость, м/с",
         legend=:bottomright, xlims=(minimum(x_l)-10, maximum(x_l)+5), ylims=(-1, 16))
    # Точки текущего положения
    scatter!([x_l[i]], [v_l[i]], label="Лидер", markersize=8, color=:black)
    scatter!([x1[i]], [v1[i]], label="Ведомый‑1", markersize=6, color=:red)
    scatter!([x2[i]], [v2[i]], label="Ведомый‑2", markersize=6, color=:green)
    scatter!([x3[i]], [v3[i]], label="Ведомый‑3", markersize=6, color=:blue)
    # Линии траекторий за последние 5 секунд
    dt = 5.0
    idx = findall(t .>= t[i] - dt)
    plot!(x_l[idx], v_l[idx], color=:black, alpha=0.5, linewidth=1, label="")
    plot!(x1[idx], v1[idx], color=:red, alpha=0.5, linewidth=1, label="")
    plot!(x2[idx], v2[idx], color=:green, alpha=0.5, linewidth=1, label="")
    plot!(x3[idx], v3[idx], color=:blue, alpha=0.5, linewidth=1, label="")
    annotate!(x_l[i], v_l[i]+0.6, text("t = $(round(t[i], digits=1)) с", 8, :black))
end
Out[0]:
No description has been provided for this image

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

In [ ]:
y_coord = 1.0
colors = [:black, :red, :green, :blue]
labels = ["Лидер", "Ведомый-1", "Ведомый-2", "Ведомый-3"]
x_min = -80
x_max = 10

@gif for i in 1:n fps = 3
    # Позиции относительно лидера
    pos_leader = x_l[i] - x_l[i]      # 0
    pos1 = x1[i] - x_l[i]
    pos2 = x2[i] - x_l[i]
    pos3 = x3[i] - x_l[i]
    positions = [pos_leader, pos1, pos2, pos3]
    speeds = [v_l[i], v1[i], v2[i], v3[i]]
    current_time = round(t[i], digits=1)
    # Базовый график
    scatter(positions, fill(y_coord, 4),
        markersize = 12,
        markercolor = colors,
        xlim = (x_min, x_max),
        ylim = (0.5, 1.5),
        xlabel = "Расстояние от лидера (м)",
        ylabel = "",
        yticks = ([y_coord], ["Дорога"]),
        legend = false,
        title = "Время: $(current_time) с, Скорость лидера: $(round(v_l[i], digits=1)) м/с"
    )
    for (j, (pos, sp)) in enumerate(zip(positions, speeds))
        if j == 1
            continue  
        else
            annotate!(pos + 2, y_coord + 0.05, text("$(round(sp, digits=1)) м/с", 8, colors[j]))
        end
    end
    for x in range(-100, 50, step=5)
        if x_min < x < x_max
            plot!([x, x], [0.6, 1.4], linewidth=0.5, color=:gray, alpha=0.4, label="")
        end
    end
end
Out[0]:
No description has been provided for this image

Из аналитики данных видно, что все ведомые движутся в фазовой области, не пересекая траекторию лидера.

Интегральная составляющая успешно компенсирует влияние шумов и неточностей, а механизм anti‑windup предотвращает перерегулирование при длительных насыщениях. В результате колонна сохраняет устойчивость на всём протяжении моделирования.

Вывод

В работе проведено сравнение двух подходов к управлению дистанцией в колонне: классического ПД-регулятора и ПИД-регулятора с защитой от виндапа. Моделирование в среде Engee показало, что:

  • ПД-регулятор прост в реализации, но страдает от установившейся ошибки, чувствителен к зоне нечувствительности и шумам, что приводит к недопустимому эффекту обгона лидера ведомыми.
  • ПИД-регулятор с антивиндапом полностью устраняет указанные недостатки. Добавление интегрального канала обеспечивает нулевую ошибку в установившемся режиме, а ограничение интегратора – устойчивость при насыщениях. Улучшенная модель демонстрирует корректное поведение колонны на длительных интервалах времени, устойчивость к шумам и задержкам, что делает её пригодной для практического применения в системах адаптивного круиз-контроля (ACC) и управления беспилотными колоннами.

Таким образом, эволюция от ПД к ПИД-регулятору с антивиндапом является необходимой для обеспечения надёжного и безопасного движения в составе колонны.