Hello,
I’m trying to modify interacting_pairs()
function for EventQueueABM model which only looks for true pairs. As EventQueueABM doesn’t have Scheduler. So, I replaced it with allids(model)
. Now this does give pairs but issue is Im only getting pairs with same property. So, if there are two agents with some symbol lets say :a and :b which are floating around in space. interacting_pairs() always finds pair with either :a, :a or :b, :b.
Here is my code:
function nearest_neighbor_new(agent::AbstractAgent, model::ABM{<:ContinuousSpace}, r)
n = nearby_ids_exact(agent.pos, model, r)
d, j = Inf, 0
for id in n
dnew = euclidean_distance(agent.pos, model[id].pos, model)
if dnew < d
d, j = dnew, id
end
end
if d == Inf
return nothing
else
return model[j]
end
end
function interacting_pairs(
model::EventQueueABM{<:ContinuousSpace},
r::Real,
exact = true,
)
pairs = Tuple{Int,Int}[]
true_pairs!(pairs, model, r)
return PairIterator(pairs, getfield(model, :agents))
end
function true_pairs!(pairs::Vector{Tuple{Int,Int}}, model::EventQueueABM{<:ContinuousSpace}, r::Real)
distances = Vector{Float64}(undef, 0)
for a in (model[id] for id in allids(model))
nn = nearest_neighbor_new(a, model, r)
nn === nothing && continue
# Sort the pair to overcome any uniqueness issues
new_pair = isless(a.id, nn.id) ? (a.id, nn.id) : (nn.id, a.id)
if new_pair ∉ pairs
# We also need to check if our current pair is closer to each
# other than any pair using our first id already in the list,
# so we keep track of nn distances.
dist = euclidean_distance(a.pos, nn.pos, model)
idx = findfirst(x -> first(new_pair) == x, first.(pairs))
if idx === nothing
push!(pairs, new_pair)
push!(distances, dist)
elseif idx !== nothing && distances[idx] > dist
# Replace this pair, it is not the true neighbor
pairs[idx] = new_pair
distances[idx] = dist
end
end
end
to_remove = Int[]
for doubles in symdiff(unique(Iterators.flatten(pairs)), collect(Iterators.flatten(pairs)))
# This list is the set of pairs that have two distances in the pair list.
# The one with the largest distance value must be dropped.
fidx = findfirst(isequal(doubles), first.(pairs))
if fidx !== nothing
lidx = findfirst(isequal(doubles), last.(pairs))
largest = distances[fidx] <= distances[lidx] ? lidx : fidx
push!(to_remove, largest)
else
# doubles are not from first sorted, there could be more than one.
idxs = findall(isequal(doubles), last.(pairs))
to_keep = findmin(map(i->distances[i], idxs))[2]
deleteat!(idxs, to_keep)
append!(to_remove, idxs)
end
end
deleteat!(pairs, unique!(sort!(to_remove)))
end
struct PairIterator{A}
pairs::Vector{Tuple{Int,Int}}
agents::Dict{Int,A}
end
Base.length(iter::PairIterator) = length(iter.pairs)
function Base.iterate(iter::PairIterator, i = 1)
i > length(iter) && return nothing
p = iter.pairs[i]
id1, id2 = p
return (iter.agents[id1], iter.agents[id2]), i + 1
end
actual code for function which is restricted to StandardABM:
function interacting_pairs(model::ABM{<:ContinuousSpace}, r::Real, method;
scheduler = abmscheduler(model), nearby_f = nearby_ids_exact,
)
@assert method ∈ (:nearest, :all, :types)
pairs = Tuple{Int,Int}[]
if method == :nearest
true_pairs!(pairs, model, r, scheduler)
elseif method == :all
all_pairs!(pairs, model, r, nearby_f)
elseif method == :types
type_pairs!(pairs, model, r, scheduler, nearby_f)
end
return PairIterator(pairs, agent_container(model))
end
function all_pairs!(
pairs::Vector{Tuple{Int,Int}},
model::ABM{<:ContinuousSpace},
r::Real, nearby_f,
)
for a in allagents(model)
for nid in nearby_f(a, model, r)
# Sort the pair to overcome any uniqueness issues
new_pair = isless(a.id, nid) ? (a.id, nid) : (nid, a.id)
new_pair ∉ pairs && push!(pairs, new_pair)
end
end
end
function true_pairs!(pairs::Vector{Tuple{Int,Int}}, model::ABM{<:ContinuousSpace}, r::Real, scheduler)
distances = Vector{Float64}(undef, 0)
for a in (model[id] for id in scheduler(model))
nn = nearest_neighbor(a, model, r)
nn === nothing && continue
# Sort the pair to overcome any uniqueness issues
new_pair = isless(a.id, nn.id) ? (a.id, nn.id) : (nn.id, a.id)
if new_pair ∉ pairs
# We also need to check if our current pair is closer to each
# other than any pair using our first id already in the list,
# so we keep track of nn distances.
dist = euclidean_distance(a.pos, nn.pos, model)
idx = findfirst(x -> first(new_pair) == x, first.(pairs))
if idx === nothing
push!(pairs, new_pair)
push!(distances, dist)
elseif idx !== nothing && distances[idx] > dist
# Replace this pair, it is not the true neighbor
pairs[idx] = new_pair
distances[idx] = dist
end
end
end
to_remove = Int[]
# `counter` counts the number of occurencies for each item, it comes from DataStructure.jl
for doubles in [k for (k,v) in counter(Iterators.flatten(pairs)) if v>1]
# This list is the set of pairs that have two distances in the pair list.
# The one with the largest distance value must be dropped.
fidx = findfirst(isequal(doubles), first.(pairs))
if fidx !== nothing
lidx = findfirst(isequal(doubles), last.(pairs))
largest = distances[fidx] <= distances[lidx] ? lidx : fidx
push!(to_remove, largest)
else
# doubles are not from first sorted, there could be more than one.
idxs = findall(isequal(doubles), last.(pairs))
to_keep = findmin(map(i->distances[i], idxs))[2]
deleteat!(idxs, to_keep)
append!(to_remove, idxs)
end
end
deleteat!(pairs, unique!(sort!(to_remove)))
end
function type_pairs!(
pairs::Vector{Tuple{Int,Int}},
model::ABM{<:ContinuousSpace},
r::Real, scheduler, nearby_f,
)
# We don't know ahead of time what types the scheduler will provide. Get a list.
available_types = unique(typeof(model[id]) for id in scheduler(model))
for id in scheduler(model)
for nid in nearby_f(model[id], model, r)
neighbor_type = typeof(model[nid])
if neighbor_type ∈ available_types && neighbor_type !== typeof(model[id])
# Sort the pair to overcome any uniqueness issues
new_pair = isless(id, nid) ? (id, nid) : (nid, id)
new_pair ∉ pairs && push!(pairs, new_pair)
end
end
end
end
struct PairIterator{A}
pairs::Vector{Tuple{Int,Int}}
agents::Dict{Int,A}
end
Base.eltype(::PairIterator{A}) where {A} = Tuple{A, A}
Base.length(iter::PairIterator) = length(iter.pairs)
function Base.iterate(iter::PairIterator, i = 1)
i > length(iter) && return nothing
p = iter.pairs[i]
id1, id2 = p
return (iter.agents[id1], iter.agents[id2]), i + 1
end