Agents.jl: Establishing a new space type

I am attempting to establish a space type relevant to a sophisticated large-scale case study in SocioEconomics field, The existing code has been translated from Python code and now I am attempting to integrate Agents.jl (with the least minimum efforts). So far I was able to imitate the agents and stepping concepts but it seems I might be misunderstanding the space concept in Agents.jl. Here is a simplified description of what has been done and would appreciate hints for corrections or improvements.

mutable struct Person <: AbstractAgent 
   id::Int 
   pos::House 
   ,,, 
end

mutable struct House <: AbstractSpace 
     town::Town
     location::Tuple{Int64,Int64} 
     occupants::Vector[Person]
     ,,, 
end 

mutable struct Town
    name::String
    location::Tuple{Int64,Int64}
    houses::Vector{House} 
    ... 
end

I have followed the instructions given in the documentation regarding establishing a new space type including providing implementation for the associated functions (e.g. nearby_ids, randomposition, etc.)

When I attempt to make use of AgentBasedModel, it does not work

model = AgentBasedModel(Person,House) 

LoadError: MethodError: no method matching AgentBasedModel(::Type{Person}, ::Type{PersonHouse})
Closest candidates are: ..

I am wondering

  1. if the space should be rather defined as a vector of Houses?
  2. if true, is there anything against a dynamic space (i.e. there are more houses during the simulation)
1 Like

If your Houses and Towns have a location::Tuple{Int64,Int64} as their position, why can’t you just use a GridSpace for your model? Maybe I’m missing an important point here.

This is something I thought about, but

  1. there is already a lot of ready code that would need adjustment (here I would be more interested in less efforts for integration)
  2. keep the code more descriptive (e.g. household of people living in the same house)
  3. the logic of coordinates does not matter that much for the current case study, e.g. I would be more interested in people living in the same town rather than the distance between people
  4. Doing this as an exercise would improve my understanding of Agents.jl
  5. The types I create could be reusable for similar case-studies in Socioeconomics
1 Like

Hi, we need to see specific code and exact error messages to give more help. Please make a runnable yet minimal working example. Modify the above code to be runnable and not have any details that are not relevant to the error message. And then paste the error message. You should not be getting the method error if you did everything properly, so the actual mistake is somewhere else.

1 Like

As you can see here: Agents.jl/model.jl at main · JuliaDynamics/Agents.jl · GitHub the method exists (SpaceType = Union{Nothing, AbstractSpace}.

AH LOL, classic beginner Julia mistake :smiley: we’ve all been here :smiley:

You need to pass in an instance of Person and an instance of the House space. You are giving their types. We want an actual instance of them. Like p = Person(1, 2); h = House(1, 2); abm = AgentBasedModel(p, h).

1 Like

Not really a beginner. I have been practising Julia for a while and gone over couple of decent books. We actually discussed this coding style once (passing types rather than using type parameters).

Then, I might be confused by the documentation, e.g.

space = GridSpaceSingle((10, 10); periodic = false)
... 
@agent SchellingAgent GridAgent{2} begin
    mood::Bool # whether the agent is happy in its position. (true = happy)
    group::Int # The group of the agent, determines mood as it interacts with neighbors
end 
...
schelling = ABM(SchellingAgent, space; properties)

The first argument (SchellingAgent) is a type.
The argument space looks like instance of a SpaceType.

ah, apologies, I read that you were translating some python code to julia so I (wrongly) assumed you were a beginner. In any case, the docs are actually clear: you need to give the agent type and an instance of a space. I was wrong to say you need to give an instance of a type.

In any situation, always looks at the docs. Agents.jl has one of the most complete and well thought out docs. It is certain 99.9% that they will give you better info than I will.

2 Likes

No problem at all. I am still enjoying the learning process in Julia. I just wrote that in case my line manager accesses this thread one day :).

I actually tried before this thread to provide an instance of a dummy house as a second argument but it did not work. But as you said, it is better to come out with a mini-example as the error could be somewhere else.

But this is where my confusion originates. What would be the relation between the space argument of ABM{A} (say ABM{A}::space) and pos field in the associated Agent A (say A::pos).

Logically, I would like to think that A::pos is simply an element within the specified ABM{A}::space. In my case, my space is a list of houses ( or a list of houses associated with towns and other details). So I guess I should re-define my space type to be a vector of houses.

Or the other option is not to care about the logic and assume that types of ABM{A}::space and A::pos are compatible.

The other issue is, the space is not really fixed. Within a simulation new houses are created. This, hopefully, does not sound to be a problem.

I will attempt to establish a mini abstract example.

1 Like

You get to decide this. Each AbstractSpace implementation also declares what the pos field of the agents must be. It depends on the space how they keep track of stuff. The spaces we have now use the agent ID to track it, not its position. Of course, the API functions move_agent! and add_agent! ensure the tracked ID and positions match as it makes intuitive sense. I’d recommend you to look at the GraphSpace implementation. It is the simplest one.

2 Likes

In case it is of any interest, I have prepared a demographic model (MiniDemographicABM.jl) which is quite an abstract dummy version of a more sophisticated model. It works fine.

The space type is simply a vector of towns (which implicitly includes all houses and their occupants, i.e. the whole population of agents). Agent::pos is a house type. There is no syntax constraint between agent::pos and model::space (except the logic needs to make sense).

Edit: to summarise, the basic types were as follows:

struct House{TownType,PersonType} 
    town::TownType
    location::NTuple{2,Int}
    occupants::Vector{PersonType}
    ... 
end 
.. 
struct TownH{HouseType}
    name::String
    density::Float64
    location::NTuple{2,Int}
    houses::Vector{HouseType}
end
... 
mutable struct PersonH{HouseType} <: AbstractAgent
    const id::Int
    pos::HouseType
    ..
end 
...
const Town = TownH{HouseTP}
const House = HouseTP{Town,PersonH}
const Person = PersonH{House}
...
abstract type PopulationSpace <: Agents.DiscreteSpace end
struct DemographicMap <: PopulationSpace
    countryname::String
    maxTownGridDim::Int
    towns::Vector{Town}
end
... 
const DemographicABM = ABM{DemographicMap}
DemographicABM(space::DemographicMap, props::DemographicABMProp) =
    ABM(Person, space; properties = props)

Sounds like to me that you could just be using a GraphSpace…? A GraphSpace is a “vector” of “nodes”, and the node can be conceptually anything; a town or a village or a universe. Each node stores a vector of the contained agents.

This was quite thinkable for me and I have had a look at the implementation of GraphSpace. Definitely I prefer to make use of existing packages / types, but all depends on the application context.

  1. Remember I am not implementing from scratch, the code in this way was mapped from existing legacy code (and the code I posted / published is a just a simplification of it, just to try the space concept)
  2. Using graphs would require conversion of the existing types and data structure (partially hard-coded, e.g. map representation with densities, simplified example UKDENSITY) and possibly some overheads in modifying the associated functions
  3. On the other side, the benefits of using graphs representation are not significant in the context of the use case I am currently busy with (i.e. population dynamics under socioeconomic context) (e.g. the logic of adding / removing nodes and edges or exploiting sophisticated graph algorithms etc. are not needed)
  4. Even an accurate map representation with edges associated with e.g. a tuple of distance and angles would be actually elegant, but still such accuracy though interesting but also not significant for the particular case study I am currently busy with (for which I still have plenty of todos and improvement potentials)
  5. using graphs could be also incorporated, in other contexts in the code, e.g. kinship relations among population. This is currently explicitly implemented in the code, see person type implementation. The question would be always what kind of benefits are obtained to pay such an overhead in re-implementing it with Graphs

Principally, I don’t exclude re-conceptualizing with graphs whether in map representation or kinship relationships one day in other case studies, especially if the benefits are significant.