Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

abmvideo for 3D Mixed-Agent Ecosystem with Pathfinding fails #1118

Open
fieldofnodes opened this issue Dec 19, 2024 · 7 comments
Open

abmvideo for 3D Mixed-Agent Ecosystem with Pathfinding fails #1118

fieldofnodes opened this issue Dec 19, 2024 · 7 comments
Labels
bug Something isn't working plotting

Comments

@fieldofnodes
Copy link

Describe the bug
The tutorial in version: 6.2 from
3D Mixed-Agent Ecosystem with Pathfinding

I have copied and ran the entire tutorial, the code blocks all execute, but when it comes to executing the abmvideo exceptions are thrown and says there is no method for animalcolor for type Animal

I had already ran the code defining the animalcolor based on agent type
Minimal Working Example
Code from tutorial:

abmvideo(
    "rabbit_fox_hawk.mp4",
    model;
    figure = (size = (800, 700),),
    frames = 300,
    framerate = 15,
    agent_color = animalcolor,
    agent_size = 1.0,
    title = "Rabbit Fox Hawk with pathfinding"
)

Give the following error:

ERROR: MethodError: no method matching animalcolor(::Animal)
The function `animalcolor` exists, but no method is defined for this combination of argument types.

If the code is runnable, it will help us identify the problem faster.
The code is from the tutorial.

Agents.jl version
v6.1.12 (Version available from general registry)
v6.2

Please provide the version you use (you can do Pkg.status("Agents").

@fieldofnodes
Copy link
Author

I tried to create an empty function
function animalcolor(::Animal) end
thinking maybe it needed to be passed, then filled appropriately when called, but I get:
ERROR: can't splice Vector{Nothing} into an OpenGL shader. Make sure all fields are of a concrete type and isbits(FieldType)-->true
Stacktrace:

@Datseris Datseris added bug Something isn't working plotting labels Dec 19, 2024
@Datseris
Copy link
Member

@jedbrown are you sure you have copy pasted every code block, in particular this one:

animalcolor(a::Rabbit) = :brown
animalcolor(a::Fox) = :orange
animalcolor(a::Hawk) = :blue

?

@fieldofnodes
Copy link
Author

Certainly a possibility to miss that. I appreciate that suggestion.

Here is the code block

using Agents, Agents.Pathfinding
using Random
import ImageMagick
using FileIO: load

@agent struct Rabbit(ContinuousAgent{3,Float64})
    energy::Float64
end

@agent struct Fox(ContinuousAgent{3,Float64})
    energy::Float64
end

@agent struct Hawk(ContinuousAgent{3,Float64})
    energy::Float64
end

@multiagent Animal(Rabbit, Fox, Hawk)


eunorm(vec) = sum(vec .^ 2)
const v0 = (0.0, 0.0, 0.0) # we don't use the velocity field here


function initialize_model(
    heightmap_url =
    "https://raw.githubusercontent.com/JuliaDynamics/" *
    "JuliaDynamics/master/videos/agents/rabbit_fox_hawk_heightmap.png",
    water_level = 8,
    grass_level = 20,
    mountain_level = 35;
    n_rabbits = 160,  ## initial number of rabbits
    n_foxes = 30,  ## initial number of foxes
    n_hawks = 30,  ## initial number of hawks
    Δe_grass = 25,  ## energy gained from eating grass
    Δe_rabbit = 30,  ## energy gained from eating one rabbit
    rabbit_repr = 0.06,  ## probability for a rabbit to (asexually) reproduce at any step
    fox_repr = 0.03,  ## probability for a fox to (asexually) reproduce at any step
    hawk_repr = 0.02, ## probability for a hawk to (asexually) reproduce at any step
    rabbit_vision = 6,  ## how far rabbits can see grass and spot predators
    fox_vision = 10,  ## how far foxes can see rabbits to hunt
    hawk_vision = 15,  ## how far hawks can see rabbits to hunt
    rabbit_speed = 1.3, ## movement speed of rabbits
    fox_speed = 1.1,  ## movement speed of foxes
    hawk_speed = 1.2, ## movement speed of hawks
    regrowth_chance = 0.03,  ## probability that a patch of grass regrows at any step
    dt = 0.1,   ## discrete timestep each iteration of the model
    seed = 42,  ## seed for random number generator
)

    # Download and load the heightmap. The grayscale value is converted to `Float64` and
    # scaled from 1 to 40
    heightmap = floor.(Int, convert.(Float64, load(download(heightmap_url))) * 39) .+ 1
    # The x and y dimensions of the pathfinder are that of the heightmap
    dims = (size(heightmap)..., 50)
    # The region of the map that is accessible to each type of animal (land-based or flying)
    # is defined using `BitArrays`
    land_walkmap = BitArray(falses(dims...))
    air_walkmap = BitArray(falses(dims...))
    for i in 1:dims[1], j in 1:dims[2]
        # land animals can only walk on top of the terrain between water_level and grass_level
        if water_level < heightmap[i, j] < grass_level
            land_walkmap[i, j, heightmap[i, j]+1] = true
        end
        # air animals can fly at any height upto mountain_level
        if heightmap[i, j] < mountain_level
            air_walkmap[i, j, (heightmap[i, j]+1):mountain_level] .= true
        end
    end

    # Generate the RNG for the model
    rng = MersenneTwister(seed)

    # Note that the dimensions of the space do not have to correspond to the dimensions
    # of the pathfinder. Discretisation is handled by the pathfinding methods
    space = ContinuousSpace((100., 100., 50.); periodic = false)

    # Generate an array of random numbers, and threshold it by the probability of grass growing
    # at that location. Although this causes grass to grow below `water_level`, it is
    # effectively ignored by `land_walkmap`
    grass = BitArray(
        rand(rng, dims[1:2]...) .< ((grass_level .- heightmap) ./ (grass_level - water_level)),
    )
    properties = (
        # The pathfinder for rabbits and foxes
        landfinder = AStar(space; walkmap = land_walkmap),
        # The pathfinder for hawks
        airfinder = AStar(space; walkmap = air_walkmap, cost_metric = MaxDistance{3}()),
        Δe_grass = Δe_grass,
        Δe_rabbit = Δe_rabbit,
        rabbit_repr = rabbit_repr,
        fox_repr = fox_repr,
        hawk_repr = hawk_repr,
        rabbit_vision = rabbit_vision,
        fox_vision = fox_vision,
        hawk_vision = hawk_vision,
        rabbit_speed = rabbit_speed,
        fox_speed = fox_speed,
        hawk_speed = hawk_speed,
        heightmap = heightmap,
        grass = grass,
        regrowth_chance = regrowth_chance,
        water_level = water_level,
        grass_level = grass_level,
        dt = dt,
    )

    model = StandardABM(Animal, space; agent_step! = animal_step!,
                        model_step! = model_step!, rng, properties)

    # spawn each animal at a random walkable position according to its pathfinder
    for _ in 1:n_rabbits
        pos = random_walkable(model, model.landfinder)
        agent = (Animal  Rabbit)(model, pos, v0, rand(abmrng(model), Δe_grass:2Δe_grass))
        add_agent_own_pos!(agent, model)
    end
    for _ in 1:n_foxes
        pos = random_walkable(model, model.landfinder)
        agent = (Animal  Fox)(model, pos, v0, rand(abmrng(model), Δe_rabbit:2Δe_rabbit))
        add_agent_own_pos!(agent, model)
    end
    for _ in 1:n_hawks
        pos = random_walkable(model, model.airfinder)
        agent = (Animal  Hawk)(model, pos, v0, rand(abmrng(model), Δe_rabbit:2Δe_rabbit))
        add_agent_own_pos!(agent, model)
    end
    return model
end



animal_step!(animal, model) = animal_step!(animal, model, variant(animal))

function animal_step!(rabbit, model, ::Rabbit)
    # Eat grass at this position, if any
    if get_spatial_property(rabbit.pos, model.grass, model) == 1
        model.grass[get_spatial_index(rabbit.pos, model.grass, model)] = 0
        rabbit.energy += model.Δe_grass
    end

    # The energy cost at each step corresponds to the amount of time that has passed
    # since the last step
    rabbit.energy -= model.dt
    # All animals die if their energy reaches 0
    if rabbit.energy <= 0
        remove_agent!(rabbit, model, model.landfinder)
        return
    end

    # Get a list of positions of all nearby predators
    predators = [
        x.pos for x in nearby_agents(rabbit, model, model.rabbit_vision) if
            variant(x) isa Fox || variant(x) isa Hawk
            ]
    # If the rabbit sees a predator and isn't already moving somewhere
    if !isempty(predators) && is_stationary(rabbit, model.landfinder)
        # Try and get an ideal direction away from predators
        direction = (0., 0., 0.)
        for predator in predators
            # Get the direction away from the predator
            away_direction = (rabbit.pos .- predator)
            # In case there is already a predator at our location, moving anywhere is
            # moving away from it, so it doesn't contribute to `direction`
            all(away_direction .≈ 0.) && continue
            # Add this to the overall direction, scaling inversely with distance.
            # As a result, closer predators contribute more to the direction to move in
            direction = direction .+ away_direction ./ eunorm(away_direction) ^ 2
        end
        # If the only predator is right on top of the rabbit
        if all(direction .≈ 0.)
            # Move anywhere
            chosen_position = random_walkable(rabbit.pos, model, model.landfinder, model.rabbit_vision)
        else
            # Normalize the resultant direction, and get the ideal position to move it
            direction = direction ./ eunorm(direction)
            # Move to a random position in the general direction of away from predators
            position = rabbit.pos .+ direction .* (model.rabbit_vision / 2.)
            chosen_position = random_walkable(position, model, model.landfinder, model.rabbit_vision / 2.)
        end
        plan_route!(rabbit, chosen_position, model.landfinder)
    end

    # Reproduce with a random probability, scaling according to the time passed each
    # step
    rand(abmrng(model)) <= model.rabbit_repr * model.dt && reproduce!(rabbit, model)

    # If the rabbit isn't already moving somewhere, move to a random spot
    if is_stationary(rabbit, model.landfinder)
        plan_route!(
            rabbit,
            random_walkable(rabbit.pos, model, model.landfinder, model.rabbit_vision),
            model.landfinder
        )
    end

    # Move along the route planned above
    move_along_route!(rabbit, model, model.landfinder, model.rabbit_speed, model.dt)
end

function animal_step!(fox, model, ::Fox)
    # Look for nearby rabbits that can be eaten
    food = [x for x in nearby_agents(fox, model) if variant(x) isa Rabbit]
    if !isempty(food)
        remove_agent!(rand(abmrng(model), food), model, model.landfinder)
        fox.energy += model.Δe_rabbit
    end


    # The energy cost at each step corresponds to the amount of time that has passed
    # since the last step
    fox.energy -= model.dt
    # All animals die once their energy reaches 0
    if fox.energy <= 0
        remove_agent!(fox, model, model.landfinder)
        return
    end

    # Random chance to reproduce every step
    rand(abmrng(model)) <= model.fox_repr * model.dt && reproduce!(fox, model)

    # If the fox isn't already moving somewhere
    if is_stationary(fox, model.landfinder)
        # Look for any nearby rabbits
        prey = [x for x in nearby_agents(fox, model, model.fox_vision) if variant(x) isa Rabbit]
        if isempty(prey)
            # Move anywhere if no rabbits were found
            plan_route!(
                fox,
                random_walkable(fox.pos, model, model.landfinder, model.fox_vision),
                model.landfinder,
            )
        else
            # Move toward a random rabbit
            plan_route!(fox, rand(abmrng(model), map(x -> x.pos, prey)), model.landfinder)
        end
    end

    move_along_route!(fox, model, model.landfinder, model.fox_speed, model.dt)
end

function animal_step!(hawk, model, ::Hawk)
    # Look for rabbits nearby
    food = [x for x in nearby_agents(hawk, model) if variant(x) isa Rabbit]
    if !isempty(food)
        # Eat (remove) the rabbit
        remove_agent!(rand(abmrng(model), food), model, model.airfinder)
        hawk.energy += model.Δe_rabbit
        # Fly back up
        plan_route!(hawk, hawk.pos .+ (0., 0., 7.), model.airfinder)
    end

    # The rest of the stepping function is similar to that of foxes, except hawks use a
    # different pathfinder
    hawk.energy -= model.dt
    if hawk.energy <= 0
        remove_agent!(hawk, model, model.airfinder)
        return
    end

    rand(abmrng(model)) <= model.hawk_repr * model.dt && reproduce!(hawk, model)

    if is_stationary(hawk, model.airfinder)
        prey = [x for x in nearby_agents(hawk, model, model.hawk_vision) if variant(x) isa Rabbit]
        if isempty(prey)
            plan_route!(
                hawk,
                random_walkable(hawk.pos, model, model.airfinder, model.hawk_vision),
                model.airfinder,
            )
        else
            plan_route!(hawk, rand(abmrng(model), map(x -> x.pos, prey)), model.airfinder)
        end
    end

    move_along_route!(hawk, model, model.airfinder, model.hawk_speed, model.dt)
end

function reproduce!(animal, model)
    animal.energy = Float64(ceil(Int, animal.energy / 2))
    new_agent = Animal(typeof(variant(animal))(model, random_position(model), v0, animal.energy))
    add_agent!(new_agent, model)
end

function model_step!(model)
    # To prevent copying of data, obtain a view of the part of the grass matrix that
    # doesn't have any grass, and grass can grow there
    growable = view(
        model.grass,
        model.grass .== 0 .& model.water_level .< model.heightmap .<= model.grass_level,
    )
    # Grass regrows with a random probability, scaling with the amount of time passing
    # each step of the model
    growable .= rand(abmrng(model), length(growable)) .< model.regrowth_chance * model.dt
end

model = initialize_model()


using GLMakie # CairoMakie doesn't do 3D plots well

animalcolor(a::Rabbit) = :brown
animalcolor(a::Fox) = :orange
animalcolor(a::Hawk) = :blue


const ABMPlot = Agents.get_ABMPlot_type()
function Agents.static_preplot!(ax::Axis3, p::ABMPlot)
    surface!(
        ax,
        (100/205):(100/205):100,
        (100/205):(100/205):100,
        p.abmobs[].model[].heightmap;
        colormap = :terrain
    )
end


abmvideo(
    "rabbit_fox_hawk.mp4",
    model;
    figure = (size = (800, 700),),
    frames = 300,
    framerate = 15,
    agent_color = animalcolor,
    agent_size = 1.0,
    title = "Rabbit Fox Hawk with pathfinding"
)

Here is the error:

ERROR: MethodError: no method matching animalcolor(::Animal)
The function `animalcolor` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  animalcolor(::Hawk)
   @ Main ~/Projects/Oxitec/genetic_modelling/src/julia-version/spatial_modelling/src/init_spatial_model.jl:305
  animalcolor(::Fox)
   @ Main ~/Projects/Oxitec/genetic_modelling/src/julia-version/spatial_modelling/src/init_spatial_model.jl:304
  animalcolor(::Rabbit)
   @ Main ~/Projects/Oxitec/genetic_modelling/src/julia-version/spatial_modelling/src/init_spatial_model.jl:303

Stacktrace:
  [1] (::AgentsVisualizations.var"#35#36"{StandardABM{}, typeof(animalcolor)})(i::Int64)
    @ AgentsVisualizations ./none:0
  [2] iterate
    @ ./generator.jl:48 [inlined]
  [3] collect
    @ ./array.jl:791 [inlined]
  [4] abmplot_colors(model::StandardABM{…}, ac::typeof(animalcolor))
    @ AgentsVisualizations ~/.julia/packages/Agents/piS7I/ext/AgentsVisualizations/src/spaces/abstract.jl:63
  [5] (::AgentsVisualizations.var"#15#19")(arg1#233::Function, arg2#234::StandardABM{…})
    @ AgentsVisualizations ./none:0
  [6] #map#13
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:570 [inlined]
  [7] map
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:568 [inlined]
  [8] lift_attributes(model::Observable{…}, ac::Observable{…}, as::Observable{…}, am::Observable{…}, offset::Observable{…})
    @ AgentsVisualizations ~/.julia/packages/Agents/piS7I/ext/AgentsVisualizations/src/abmplot.jl:197
  [9] plot!(p::MakieCore.Plot{AgentsVisualizations._abmplot, Tuple{ABMObservable{…}}})
    @ AgentsVisualizations ~/.julia/packages/Agents/piS7I/ext/AgentsVisualizations/src/abmplot.jl:146
 [10] connect_plot!(parent::Scene, plot::MakieCore.Plot{AgentsVisualizations._abmplot, Tuple{ABMObservable{…}}})
    @ Makie ~/.julia/packages/Makie/Y3ABD/src/interfaces.jl:395
 [11] plot!
    @ ~/.julia/packages/Makie/Y3ABD/src/interfaces.jl:412 [inlined]
 [12] plot!(ax::Axis3, plot::MakieCore.Plot{AgentsVisualizations._abmplot, Tuple{ABMObservable{…}}})
    @ Makie ~/.julia/packages/Makie/Y3ABD/src/figureplotting.jl:412
 [13] _create_plot!(::Function, ::Dict{…}, ::Axis3, ::ABMObservable{…})
    @ Makie ~/.julia/packages/Makie/Y3ABD/src/figureplotting.jl:381
 [14] _abmplot!(::Axis3, ::Vararg{…}; kw::@Kwargs{})
    @ AgentsVisualizations ~/.julia/packages/MakieCore/EU17Y/src/recipes.jl:195
 [15] abmplot!(ax::Axis3, abmobs::ABMObservable{…}; params::Dict{…}, add_controls::Bool, enable_inspection::Bool, enable_space_checks::Bool, kwargs::@Kwargs{})
    @ AgentsVisualizations ~/.julia/packages/Agents/piS7I/ext/AgentsVisualizations/src/abmplot.jl:80
 [16] abmplot!
    @ ~/.julia/packages/Agents/piS7I/ext/AgentsVisualizations/src/abmplot.jl:61 [inlined]
 [17] #abmplot!#3
    @ ~/.julia/packages/Agents/piS7I/ext/AgentsVisualizations/src/abmplot.jl:38 [inlined]
 [18] abmplot!
    @ ~/.julia/packages/Agents/piS7I/ext/AgentsVisualizations/src/abmplot.jl:22 [inlined]
 [19] abmplot(model::StandardABM{…}; figure::@NamedTuple{}, axis::@NamedTuple{}, warn_deprecation::Bool, kwargs::@Kwargs{})
    @ AgentsVisualizations ~/.julia/packages/Agents/piS7I/ext/AgentsVisualizations/src/abmplot.jl:9
 [20] abmvideo(file::String, model::StandardABM{…}; spf::Nothing, dt::Int64, framerate::Int64, frames::Int64, title::String, showstep::Bool, figure::@NamedTuple{}, axis::@NamedTuple{}, recordkwargs::@NamedTuple{}, kwargs::@Kwargs{})
    @ AgentsVisualizations ~/.julia/packages/Agents/piS7I/ext/AgentsVisualizations/src/convenience.jl:115
 [21] top-level scope
    @ ~/Projects/Oxitec/genetic_modelling/src/julia-version/spatial_modelling/src/init_spatial_model.jl:320
Some type information was truncated. Use `show(err)` to see complete types.

@Datseris
Copy link
Member

Okay this seems like an un-updated example due to the changes of @multiagent. I am confused however as to why the online example builds fine and displays the video... @Tortar this is for you I think.

@fieldofnodes
Copy link
Author

Thanks for the response. So does @multiagent have some new features? In the case is Animal an abstract type?

@fieldofnodes
Copy link
Author

Okay this seems like an un-updated example due to the changes of @multiagent. I am confused however as to why the online example builds fine and displays the video... @Tortar this is for you I think.

Actually on my browser (at least Opera) I only see the video layout, not the actual video.

@Datseris
Copy link
Member

See the new Tutorial for how to dispatch on multi agent types. You need the variantof function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working plotting
Projects
None yet
Development

No branches or pull requests

2 participants