Engee 文档
Notebook

无人机群模型

该示例考虑了用于无人驾驶飞行器(Uav)的基于智能体的集成群控制模型,以执行在有限范围内搜索和摧毁移动目标的任务。 该代码封装了群体行为的关键方面:分散管理,情况的联合映射,在有限范围内的代理之间的通信,以及合作决策以攻击需要同时使用多个无人机来中和

本项目中使用了以下库和工具:

using Random-提供用于生成伪随机数的函数,这对于模拟无人机和目标的随机行为至关重要。

using LinearAlgebra-提供线性代数运算的工具,例如计算向量的范数(norm),用于计算代理之间的距离。

using Statistics-包含基本的统计功能,包括计算平均值(mean)来分析仿真结果。

gr()-激活绘图的GR后端。jl库,它提供了swarm动画的高速和高质量可视化。

In [ ]:
using Random
using LinearAlgebra
using Statistics
gr()
Out[0]:
Plots.GRBackend()

现在让我们来看看项目中使用的对象的结构。

结构:SimulationConfig

用途:存储所有仿真配置参数,确保实验管理的灵活性和易用性。

  1. world_size::Tuple{Int, Int}-虚拟多边形的尺寸(宽度和高度)在常规单位,限制代理的运动面积。
  2. n_drones::Int-群中无人机的总数。
  3. n_targets::Int-范围内的目标总数。
  4. **dt::Float64**是积分运动方程的时间步长(时间增量)的值,它决定了模拟的离散度。
  5. **total_time::Int**是模拟的总持续时间,以时间步数(帧数)衡量。
  6. comm_range::Float64-两架无人机可以交换数据的最大范围(通信半径)。
  7. sensor_range::Float64-无人机传感器检测目标的最大范围(观察半径)。
  8. drone_speed::Float64-搜索模式下的基本无人机移动速度。
  9. target_speed::Float64-基本目标移动速度。
  10. **seed::Int**是伪随机数发生器的种子,可确保实验结果的重现性。

结构:无人机

目的:描述群中一个无人机代理的状态和行为。 它是可变的,因为它的字段在模拟过程中会主动更新。

  1. **id::Int**是无人机的唯一标识符。
  2. pos::Vector{Float64}-矢量与无人机在飞机上的当前坐标 [x, y].
  3. vel::Vector{Float64}-具有当前无人机速度分量的矢量 [vx, vy].
  4. state::Symbol-无人机的当前状态(:searching -搜索, :diving -攻击),这决定了他的行为。
  5. **map::Matrix{Float64}**是已知目标的局部地图,它是一个矩阵,其中 1.0 标检测到的目标的坐标。
  6. target_id::Union{Int, Nothing}-无人机当前正在攻击的目标的ID; nothing 如果无人机处于搜索模式。
  7. **comm_range::Float64**是特定无人机的通信半径(可用于模拟异构群)。
  8. sensor_range::Float64-特定无人机的观看半径。
  9. failed_attack::Bool-指示不成功攻击尝试的标志(需要在视觉上突出显示这样的无人机并更改其逻辑)。
  10. target_destroyed::Bool-指示无人机是否已被摧毁的标志。

结构:目标

目的:目标状态的描述。 它也是多变的,因为它的状态和攻击无人机的列表在模拟过程中会发生变化。

  1. **id::Int**是目标的唯一标识符。
  2. pos::Vector{Float64}-矢量与目标的当前坐标 [x, y].
  3. **vel::Vector{Float64}**是目标速度的当前分量的矢量 [vx, vy].
  4. required_drones::Int-同时攻击摧毁目标所需的无人机数量(合作阈值)。
  5. **locked_by::Vector{Int}**是包含 id 当前正在攻击("捕获")给定目标的无人机。
  6. destroyed::Bool-指示目标是否已被摧毁的标志。
In [ ]:
struct SimulationConfig
    world_size::Tuple{Int, Int}
    n_drones::Int
    n_targets::Int
    dt::Float64
    total_time::Int
    comm_range::Float64
    sensor_range::Float64
    drone_speed::Float64
    target_speed::Float64
    seed::Int
end
mutable struct Drone
    id::Int
    pos::Vector{Float64}
    vel::Vector{Float64}
    state::Symbol
    map::Matrix{Float64}
    target_id::Union{Int, Nothing}
    comm_range::Float64
    sensor_range::Float64
    failed_attack::Bool
    target_destroyed::Bool
end
mutable struct Target
    id::Int
    pos::Vector{Float64}
    vel::Vector{Float64}
    required_drones::Int
    locked_by::Vector{Int}
    destroyed::Bool
end

下面的代码使用模拟参数初始化全局常量,创建一个空的映射,并将基本的实验设置输出到控制台以进行视觉检查。

In [ ]:
const config = SimulationConfig((200, 200), 10, 7, 0.5, 700, 100.0, 20.0, 3.0, 0.5, 123)
const empty_map = zeros(config.world_size...)
println("Конфигурация:")
println("  Дроны: $(config.n_drones)")
println("  Цели: $(config.n_targets)")
println("  Размер мира: $(config.world_size)")
println("  Диапазон связи: $(config.comm_range)")
println("  Диапазон сенсора: $(config.sensor_range)")
Конфигурация:
  Дроны: 10
  Цели: 7
  Размер мира: (200, 200)
  Диапазон связи: 100.0
  Диапазон сенсора: 20.0

接下来,我们来看看这个例子中使用的辅助函数。

功能 initialize_drones 创建并返回一个向量 n 无人机,其中每一个随机放置在游戏世界的四个边界之一,并接收指向多边形内的初始速度,以及初始化代理的所有起始参数,包括其唯一标识符,空地图,

In [ ]:
function initialize_drones(n, world_size, comm_range, sensor_range)
    drones = Vector{Drone}(undef, n)
    sides = rand(1:4, n)
    for i in 1:n
        pos, vel = if sides[i] == 1
            ([rand() * world_size[1], world_size[2]], [0.0, -rand(1.0:config.drone_speed)])
        elseif sides[i] == 2
            ([world_size[1], rand() * world_size[2]], [-rand(1.0:config.drone_speed), 0.0])
        elseif sides[i] == 3
            ([rand() * world_size[1], 0.0], [0.0, rand(1.0:config.drone_speed)])
        else
            ([0.0, rand() * world_size[2]], [rand(1.0:config.drone_speed), 0.0])
        end
        drones[i] = Drone(i, pos, vel, :searching, copy(empty_map), nothing, comm_range, sensor_range, false, false)
    end
    return drones
end
Out[0]:
initialize_drones (generic function with 1 method)

功能 initialize_targets 创建并返回一个向量 n 目标,每个目标被放置在世界中心区域的伪随机位置(与边界偏移)并接收随机速度矢量,以及初始化其参数,包括标识符,需要摧毁的无人机数量(默认为1),

In [ ]:
function initialize_targets(n, world_size)
    targets = Vector{Target}(undef, n)
    for i in 1:n
        pos = [rand(10:world_size[1]-10), rand(10:world_size[2]-10)]
        vel = [rand(-1.0:0.1:1.0), rand(-1.0:0.1:1.0)] * config.target_speed
        targets[i] = Target(i, pos, vel, 1, Int[], false)
    end
    return targets
end
Out[0]:
initialize_targets (generic function with 1 method)

功能 update_position! 根据代理(无人机或目标)的速度和时间步长更新代理(无人机或目标)的位置,通过将代理放置在多边形内并计算一个新的随机速度矢量,该矢量指向从法线到墙的±60度的角度,确保从边界的正确反射,但前提是代理是一个活动的,而不是被破坏的目标。

In [ ]:
function update_position!(agent, dt, world_size)
    if !(isa(agent, Target) && agent.destroyed)
        agent.pos .+= agent.vel .* dt
        hit_wall = false
        for i in 1:2
            if agent.pos[i] < 0
                agent.pos[i] = 0
                hit_wall = true
            elseif agent.pos[i] > world_size[i]
                agent.pos[i] = world_size[i]
                hit_wall = true
            end
        end
        if hit_wall
            θ = rand(-π/3:0.1:π/3)
            speed = max(norm(agent.vel), 1.0)
            agent.vel = [cos(θ) -sin(θ); sin(θ) cos(θ)] * [speed, 0.0]
            for i in 1:2
                if agent.pos[i] == 0
                    agent.vel[i] = abs(agent.vel[i])
                elseif agent.pos[i] == world_size[i]
                    agent.vel[i] = -abs(agent.vel[i])
                end
            end
        end
    end
end
Out[0]:
update_position! (generic function with 1 method)

功能 sense_and_act! 实现单个无人机的目标检测和决策逻辑:如果无人机没有处于攻击模式,没有失败,也没有摧毁目标,它会扫描其视野范围内的空间,用找到的目标的坐标更新地图,如果目标需要更多的无人机攻击,则切换到攻击模式(:diving),阻止目标并将您的ID添加到其攻击者列表中。

In [ ]:
function sense_and_act!(drone, targets, world_size)
    (drone.state == :diving || drone.failed_attack || drone.target_destroyed) && return nothing
    for target in targets
        target.destroyed && continue
        dist = norm(drone.pos - target.pos)
        if dist < drone.sensor_range
            x_idx = Int(clamp(round(target.pos[1]), 1, world_size[1]))
            y_idx = Int(clamp(round(target.pos[2]), 1, world_size[2]))
            drone.map[x_idx, y_idx] = 1.0
            if length(target.locked_by) < target.required_drones && drone.target_id === nothing
                drone.state = :diving
                drone.target_id = target.id
                push!(target.locked_by, drone.id)
                break
            end
        end
    end
end
Out[0]:
sense_and_act! (generic function with 1 method)

功能 communicate! 实现了一种在无人机之间交换信息的机制:对于每对无人机,检查它们之间的距离,如果它小于两者的通信半径,则通过组合已知坐标同步它们的目标图(

In [ ]:
function communicate!(drones)
    n = length(drones)
    for i in 1:n
        for j in i+1:n
            dist = norm(drones[i].pos - drones[j].pos)
            if dist < min(drones[i].comm_range, drones[j].comm_range)
                for x in 1:size(drones[i].map, 1)
                    for y in 1:size(drones[i].map, 2)
                        if drones[i].map[x, y] == 1.0 || drones[j].map[x, y] == 1.0
                            drones[i].map[x, y] = 1.0
                            drones[j].map[x, y] = 1.0
                        end
                    end
                end
            end
        end
    end
end
Out[0]:
communicate! (generic function with 1 method)

功能 dive_to_target! 控制无人机攻击过程:如果目标已经被摧毁,无人机切换到搜索模式,否则无人机向目标移动,当接近临界距离时,以80%的概率摧毁目标(如果达到所需数量的

In [ ]:
function dive_to_target!(drone, targets, dt, world_size)
    if drone.state == :diving && drone.target_id !== nothing
        target = targets[drone.target_id]
        if target.destroyed
            drone.state = :searching
            drone.target_id = nothing
            drone.target_destroyed = true  
            return
        end
        direction = target.pos - drone.pos
        norm_dir = norm(direction)
        if norm_dir > 1.0
            drone.vel = direction / norm_dir * 5.0
        else
            drone.vel .= 0.0
            if rand() < 0.8 && length(target.locked_by) >= target.required_drones
                target.destroyed = true
                target.vel .= 0.0
                drone.target_destroyed = true 
            else
                drone.failed_attack = true
                drone.state = :searching
                drone.vel .= 0.0
                filter!(x -> x != drone.id, target.locked_by)
            end
        end
        update_position!(drone, dt, world_size)
    end
end
Out[0]:
dive_to_target! (generic function with 1 method)

功能 create_plot 可视化模拟的当前状态:显示目标(红色-活动,灰色-破坏),有关攻击无人机的数量信息,各种状态的无人机(完成任务或失败后蓝色-搜索,绿色-攻击,灰色-不活动),

In [ ]:
function create_plot(drones, targets, frame, world_size, destroyed_count, attacking_count)
    p = plot(size=(800, 800), xlim=(0, world_size[1]), ylim=(0, world_size[2]), legend=false, title="Рой дронов - Кадр: $frame/$(config.total_time)")
    for target in targets
        color = target.destroyed ? :gray : :red
        scatter!([target.pos[1]], [target.pos[2]], color=color, markersize=6, marker=:square, alpha=0.8)
        if !target.destroyed
            annotate!(target.pos[1] + 3, target.pos[2] + 3, text($(target.id)($(length(target.locked_by))/$(target.required_drones))", 8))
        end
    end
    for drone in drones
        color = if drone.failed_attack || drone.target_destroyed  # внесены изменения
            :gray
        elseif drone.state == :searching
            :blue
        elseif drone.state == :diving
            if drone.target_id !== nothing && targets[drone.target_id].destroyed
                :gray
            else
                :green
            end
        else
            :purple
        end
        scatter!([drone.pos[1]], [drone.pos[2]], color=color, markersize=7, marker=:utriangle, alpha=0.9)
        if drone.state == :searching && !drone.failed_attack && !drone.target_destroyed  # внесены изменения
            plot!([drone.pos[1] + drone.sensor_range * cos(θ) for θ in 0:0.2:2π],
                  [drone.pos[2] + drone.sensor_range * sin(θ) for θ in 0:0.2:2π],
                  linecolor=color, linealpha=0.1, linewidth=1, seriestype=:path)
        end
        annotate!(drone.pos[1] - 6, drone.pos[2] - 6, text($(drone.id)", 8, color))
    end
    annotate!(5, world_size[2] - 5, text("Уничтожено: $(destroyed_count)/$(config.n_targets)", 10, :black))
    annotate!(5, world_size[2] - 15, text("Атакующие: $(attacking_count)", 10, :black))
    annotate!(5, world_size[2] - 25, text("Кадр: $frame/$(config.total_time)", 10, :black))
    return p
end
Out[0]:
create_plot (generic function with 1 method)

功能 run_simulation 它是主要的仿真周期:初始化无人机和目标,然后在每个时间步更新目标位置,通过无人机进行目标检测,组织无人机之间的通信,更新主动无人机的位置,可视化系统状态,跟踪目标破坏时间,并在仿真完成后输出最终统计信息。

In [ ]:
function run_simulation()
    Random.seed!(config.seed)
    drones = initialize_drones(config.n_drones, config.world_size, config.comm_range, config.sensor_range)
    targets = initialize_targets(config.n_targets, config.world_size)
    frames_to_destroy = Int[]
    target_destroy_times = Dict{Int, Int}()
    destroyed_count = 0
    anim = @animate for frame in 1:config.total_time
        attacking_count = count(d -> d.state == :diving, drones)
        current_destroyed = count(t -> t.destroyed, targets)
        if current_destroyed > destroyed_count
            for target in targets
                if target.destroyed && !haskey(target_destroy_times, target.id)
                    target_destroy_times[target.id] = frame
                    push!(frames_to_destroy, frame)
                end
            end
            destroyed_count = current_destroyed
        end
        if destroyed_count == config.n_targets
            println("Все цели уничтожены на кадре $frame")
            break
        end
        for target in targets
            update_position!(target, config.dt, config.world_size)
        end
        for drone in drones
            sense_and_act!(drone, targets, config.world_size)
        end
        communicate!(drones)
        for drone in drones
            if drone.state == :searching && !drone.failed_attack && !drone.target_destroyed  # внесены изменения
                update_position!(drone, config.dt, config.world_size)
            elseif drone.state == :diving
                dive_to_target!(drone, targets, config.dt, config.world_size)
            end
        end
        p = create_plot(drones, targets, frame, config.world_size, destroyed_count, attacking_count)
    end every 5
    println("\n" * "="^50)
    println("ИТОГИ СИМУЛЯЦИИ")
    println("="^50)
    println("Уничтожено целей: $(destroyed_count)/$(config.n_targets)")
    if !isempty(frames_to_destroy)
        println("Среднее время уничтожения: $(round(mean(frames_to_destroy), digits=1)) кадров")
        println("Максимальное время: $(maximum(frames_to_destroy)) кадров")
        println("Минимальное время: $(minimum(frames_to_destroy)) кадров")
    end
    return anim
end
Out[0]:
run_simulation (generic function with 1 method)

现在让我们运行模拟并查看其结果。

In [ ]:
println("Запуск симуляции...")
animation = run_simulation()
gif(animation, "drone_swarm.gif", fps=10)
Запуск симуляции...

==================================================
ИТОГИ СИМУЛЯЦИИ
==================================================
Уничтожено целей: 5/7
Среднее время уничтожения: 177.4 кадров
Максимальное время: 457 кадров
Минимальное время: 24 кадров
[ Info: Saved animation to /user/drone_swarm.gif
Out[0]:
No description has been provided for this image

结论

这个项目是一个很好的例子模拟复杂系统与分散管理。 它教授基于代理的建模原则,其中每个无人机都有自己的行为逻辑和本地信息,但能够通过有限的通信进行协作交互。

最有趣的方面:

-在没有中央控制者的情况下实施集体决策机制
-模拟有限的感觉区域和通信半径
-需要多个无人机同时参与的目标的合作攻击
-动态适应不断变化的环境和处理不成功的攻击
-实时可视化与代理状态的颜色指示

该代码演示了单个级别的一组简单规则如何在系统级别生成复杂的组行为,这是群体智能和多代理系统中的关键原则。