# ABM Agents.jl model properties as a nested dictionary

**URL:** <https://discourse.julialang.org/t/abm-agents-jl-model-properties-as-a-nested-dictionary/123110>\
**Category:** Modelling & Simulations\
**Tags:** agents, abm, agentsjl\
**Created:** [November 26, 2024, 3:28pm UTC](https://discourse.julialang.org/t/abm-agents-jl-model-properties-as-a-nested-dictionary/123110 "2024-11-26T15:28:08Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![elisadonati23](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elisadonati23/32/205472_2.png) [@elisadonati23](https://discourse.julialang.org/u/elisadonati23)\
**Post date:** [November 26, 2024, 3:28pm UTC](https://discourse.julialang.org/t/abm-agents-jl-model-properties-as-a-nested-dictionary/123110/1 "2024-11-26T15:28:08Z")

</div>

Dear Julia and the Agents.jl community,

I’m currently working on a multispecies agent-based model (ABM) with sardine and anchovy superindividuals, and I’m planning to transition to the multi-agent framework soon. My model includes numerous properties because it is a bioenergetic model with over 50 parameters for each species. These parameters change depending on the agent species, and I need my step functions to access them.

To organize these properties, I tried to set up a nested dictionary structure for the model parameters. Here’s a simplified version of my property function generator:

```julia
@agent struct Fish(NoSpaceAgent)
    # Basic characteristics
    species::Symbol # :sardine, :anchovy
    type::Symbol # :eggmass, :juvenile, :adult
end

function create_params_dict(Meggsardine, Megganchovy)

    # Define the dictionary
    model_parameters = Dict(
        :natural_mortalities => Dict(
            :sardine => Dict(
                :M_egg => Meggsardine,
                :Madults => 4.0),
            :anchovy => Dict(
                :M_egg => Megganchovy,
                :M0 => 5.0)
                ),
        :bioparams => Dict(
            :sardine => Dict(
                :energy => 5.0,
                :growth=> 2.5),
            :anchovy => Dict(
                :energy => 3,0,
                :growth => 8.0)
                ),
        :output => Dict(
            :sardine => Dict(
                :TotB => 0.0),
            :anchovy => Dict(
                :TotB => 0.0)
                ))

    return model_parameters
end

```

In this approach, I can provide different function arguments depending on the simulation goal, initialize fixed parameters, and set output keys to zero. The motivation behind using nested dictionaries is to avoid adding suffixes to parameter names to distinguish which species they refer to. As mentioned, I have many more parameters in the full model.

I have several different step functions in my model. While I initially considered using function dispatch based on the agent type, the code was becoming quite lengthy and functions were doing the same job just with different set of parameters. Instead, I opted to structure my step functions so that they access the appropriate subset of model properties depending on the agent’s `:species`. This way, each species-specific data is retrieved dynamically within the corresponding step function.

```julia
function egghatch!(Fish, model)
    if is_sardine(Fish)
        deb_species = NamedTuple(model.bioparams[:sardine])
    else
        deb_species = NamedTuple(model.bioparams[:anchovy])
    end

* function code*
        end

```

However, I’m running into issues when using functions like `init_model_data()` and `collect_model_data()`. The problem arises because the model data (`mdata`) can be either a vector or a generator function, and I’m unsure how to handle this effectively with my current dictionary structure.

Has anyone else dealt with a similar challenge? Any advice on how to improve the structure for better compatibility with `init_model_data()` and `collect_model_data()` in a multi-agent context would be greatly appreciated.

I’m sure there’s a more efficient and scalable way to structure the simulation framework.

Thanks in advance!

---

<div class="post-metadata">

**Author:** ![Datseris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/datseris/32/13406_2.png) [@Datseris](https://discourse.julialang.org/u/Datseris)\
**Post date:** [November 26, 2024, 10:58pm UTC](https://discourse.julialang.org/t/abm-agents-jl-model-properties-as-a-nested-dictionary/123110/2 "2024-11-26T22:58:10Z")

</div>

> [@elisadonati23](#):
>
> The problem arises because the model data (`mdata`) can be either a vector or a generator function, and I’m unsure how to handle this effectively with my current dictionary structure.

I am sorry, I don’t understand the problem. Can you please try to rephrase it, or give a **concrete** (runnable) example of the problem?

I also have not understood what you wish to achieve with `init_model_data()` as this is a low level function that really shouldn’t be called by the user directly unless the user is implementing an advanced and custom data collection routine that does not appear to be the case for you.

As far as I can tell if you wanted a particular set of fields from your model parameter dictionary you could make the following:

```julia
mdata = [
model -> model.bioparams[:sardine],
model -> model.bioparams[:anchovy],
]

```

this is completely fine code and when given to `run!` it would extract two model data, the sardine and anchovy bioparameters.

---

<div class="post-metadata">

**Author:** ![elisadonati23](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elisadonati23/32/205472_2.png) [@elisadonati23](https://discourse.julialang.org/u/elisadonati23)\
**Post date:** [November 28, 2024, 5:01pm UTC](https://discourse.julialang.org/t/abm-agents-jl-model-properties-as-a-nested-dictionary/123110/3 "2024-11-28T17:01:21Z")

</div>

> [@Datseris](#):
>
> ```julia
> mdata = [
> model -> model.bioparams[:sardine],
> model -> model.bioparams[:anchovy],
> ]
> 
> ```

Thank you for replying! I will come asap with a runnable easy example, thank you!

---

<div class="post-metadata">

**Author:** ![elisadonati23](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elisadonati23/32/205472_2.png) [@elisadonati23](https://discourse.julialang.org/u/elisadonati23)\
**Post date:** [November 29, 2024, 1:22pm UTC](https://discourse.julialang.org/t/abm-agents-jl-model-properties-as-a-nested-dictionary/123110/4 "2024-11-29T13:22:28Z")

</div>

@Datseris Dear George, thank you! your `mdata` version worked with the `init_model_data`() function. When I started using Agents.jl, I went through some of the examples and saw the possibility of using that function to initialize the `mdata` ad `adata` as dataframes. But I couldn’t make it work with a nested `mdata` dictionary.

To be complete, here it is a simpler and runnable version of how I set up the framework of my simulation. Nested model properties because I have many parameters and a `complex_step`() function to run specific `agent_step` functions at certain moments (also with @`thread`).

Hope you have a look and tell if there is something that could be definitely improved:  
Thank you for helping again!

```julia
# WORKING CODE
using Agents

@agent struct Fish(NoSpaceAgent)
    # Basic characteristics
    species::Symbol # :sardine, :anchovy
    type::Symbol
    energy::Float64 # :eggmass, :juvenile, :adult
end

# simplified model properties
function create_params_dict(Nsardine, Nanchovy)
    # Define the dictionary
    model_parameters = Dict(
        :environment => Dict(
            :time_step => 1.0,
            :Nsardine => Nsardine,
            :Nanchovy => Nanchovy,
        ),    
        :bioparams => Dict(
            :sardine => Dict(
                :growth=> 2.5),
            :anchovy => Dict(
                :growth => 8.0)
                ),
        :output => Dict(
            :sardine => Dict(
                :TotB => 0.0),
            :anchovy => Dict(
                :TotB => 0.0)
                ))
    return model_parameters
end

#stepping function

function grow(agent::Fish, model::ABM)

    #same function for both species but different params must be accessed
    # Get the energy and growth rate of the species
    growth = model.bioparams[agent.species][:growth]

    # Update the energy of the agent
    agent.energy += growth

    # If the agent has enough energy, it reproduces
    if agent.energy > 10.0
        add_agent!(Fish, model, agent.species, :juvenile, 1.0)
        if agent.species == :sardine
            model.environment[:Nsardine] += 1
        else
            model.environment[:Nanchovy] += 1
        end
        agent.energy = 0.0
    end
    return
end

function environment(model::ABM)
    # Update the environment and calculate biomass
    model.environment[:time_step] += 1

    # Update the total biomass
    model.output[:sardine][:TotB] = model.environment[:Nsardine] *
                                                 model.bioparams[:sardine][:growth]
    model.output[:anchovy][:TotB] = model.environment[:Nanchovy] *
                                                 model.bioparams[:anchovy][:growth]
end

function complex_step(model::ABM)

    # simplification of my model: I need a complex stepping
    # since I use @threads and call agent step functions in specific moment of the simulation

    # Update the agents - here I use @threads in my sim
    for agent in allagents(model)
        grow(agent, model)
    end
    # Update the environment
    environment(model)
end

function model_initialize(Nsardine, Nanchovy)
    properties = create_params_dict(Nsardine, Nanchovy) 
    model = ABM(
        Fish;
        properties = properties,
        model_step! = complex_step
    )

    for _ in 1:Nsardine
        add_agent!(Fish, model, :sardine, :adult, 1.0)
    end

    for _ in 1:Nanchovy
        add_agent!(Fish, model, :anchovy, :adult, 1.0)
    end
return model
end

abm = model_initialize(1, 1)

adata = [:type, :species, :energy]
mdata = [model -> model.environment[:time_step], 
model -> model.environment[:Nsardine], 
model -> model.environment[:Nanchovy], 
model -> model.output[:sardine][:TotB], 
model -> model.output[:anchovy][:TotB]]

results = []
df_agent = init_agent_dataframe(abm, adata)
df_model = init_model_dataframe(abm, mdata)

# Run the simulation
df_agent = run!(abm, 20; adata, mdata)

push!(results, df_agent)

```
