Какой элегантный способ оживить рецепт сюжета Mak ie .jl для Agents.jl в Юлии? - PullRequest
4 голосов
/ 12 апреля 2020

Допустим, у нас есть следующий рабочий процесс Agents.jl + Mak ie .jl:

using Agents, Random, AgentsPlots, Makie, Observables


mutable struct BallAgent <: AbstractAgent
    id::Int
    pos::Tuple{Float64, Float64}
    vel::Tuple{Float64, Float64}
    mass::Float64
end

function ball_model(; speed = 0.002)
    space2d = ContinuousSpace(2; periodic = true, extend = (1, 1))
    model = AgentBasedModel(BallAgent, space2d, properties = Dict(:dt => 1e0, :i => Observable(0)))

    Random.seed!(1001)

    for ind ∈ 1:500
        pos = Tuple(rand(Float64, 2))
        vel = (sincos(rand(Float64) * 2π) |> reverse) .* speed
        add_agent!(pos, model, vel, 1e0)
    end
    index!(model)

    return model
end

agent_step!(agent::BallAgent, model) = move_agent!(agent, model, model.dt)

function AbstractPlotting.:plot!(scene::AbstractPlotting.Plot(AgentBasedModel))
    ab_model = scene[1][]
    position = Observable([a.pos for a ∈ allagents(ab_model)])

    on(ab_model.i) do i
        position[] = [a.pos for a ∈ allagents(ab_model)]
    end

    scatter!(scene, position, markersize = 0.01)
end


function create_animation()
    model = ball_model()

    scene = plot(model)
    display(AbstractPlotting.PlotDisplay(), scene)
    for i ∈ 1:600
        Agents.step!(model, agent_step!, 1)
        model.i[] = i
        sleep(1/60)
    end
end

Теперь, так как AgentsPlot.jl не поддерживает Mak ie, я должен сделать рецепт для этого и в настоящее время я обновляю график путем регистрации обратного вызова, который обновляет наблюдаемые позиции, которые передаются в разброс.

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

model = AgentBasedModel(BallAgent, space2d, properties = Dict(:dt => 1e0, :i => Observable(0))) i - это наблюдаемая вещь, к которой мы прикрепляем обратный вызов, например:

  on(ab_model.i) do i
        position[] = [a.pos for a ∈ allagents(ab_model)]
    end

, и я должен обновить это здесь, поэтому обратный вызов вызывается так:

  for i ∈ 1:600
        model.i[] = i

1 Ответ

2 голосов
/ 12 апреля 2020

Одним из способов сделать его более элегантным является добавление обратного вызова к самой модели, а затем использование Observables.notify!(model) для запуска обратного вызова (-ей). Таким образом, вам не понадобится другая переменная для отслеживания, но я все еще чувствую, что ее можно сделать более элегантной.

using Agents, Random, AgentsPlots, Makie, Observables


mutable struct BallAgent <: AbstractAgent
    id::Int
    pos::Tuple{Float64, Float64}
    vel::Tuple{Float64, Float64}
    mass::Float64
end

function ball_model(; speed = 0.002)
    space2d = ContinuousSpace(2; periodic = true, extend = (1, 1))
    model = AgentBasedModel(BallAgent, space2d, properties = Dict(:dt => 1e0))

    Random.seed!(1001)

    for ind ∈ 1:500
        pos = Tuple(rand(Float64, 2))
        vel = (sincos(rand(Float64) * 2π) |> reverse) .* speed
        add_agent!(pos, model, vel, 1e0)
    end
    index!(model)

    return model
end

agent_step!(agent::BallAgent, model) = move_agent!(agent, model, model.dt)

function AbstractPlotting.:plot!(scene::AbstractPlotting.Plot(AgentBasedModel))
    model = scene[1]
    position = Observable([a.pos for a ∈ allagents(model[])])

    on(model) do _model
        position[] = [a.pos for a ∈ allagents(_model)]
    end

    scatter!(scene, position, markersize = 0.01)
end


function create_animation()
    model = Observable(ball_model())

    scene = plot(model)
    display(AbstractPlotting.PlotDisplay(), scene)
    for i ∈ 1:600
        Agents.step!(model[], agent_step!, 1)
        Observables.notify!(model)

        sleep(1/60)
    end
end

Редактировать: Observables.notify!(model) просто делает model[] = model[]

...