How to narrow the variants of agents returned by `random_agent`?

Hello! Is there a way to tell random_agent which variant of a @multiagent model to return?

My specific problem is this:

@agent struct Supplier(NoSpaceAgent)
    sellingPrice::Float64 = 1
end

@agent struct Buyer(NoSpaceAgent)
    supplier::Supplier
end

@multiagent MarketActor(Buyer, Supplier)

# […]

# When initially adding agents to the model
supplier = random_agent(model, a -> variantof(a) <: Supplier) # `supplier` is a MarketActor
add_agent!(MarketActor ∘ Buyer, model; supplier) # which cannot be converted to a narrower type(?)

I guess I could just store IDs instead of an actual pointer to the supplier, but that seems… less clean? I don’t know.

Thank you in advance!

EDIT: I think the more general question is how to, in general, “narrow” agent variants? Is this possible?

1 Like

Can you clarify whether your question is about (a) altering the return value of random_agent or (b) using the current return value in add_agent!? Sounds like different questions to me. If your question is both that’s okay as well :smiley:

Hey! Thanks for the help :slight_smile:

Well, I think it’s both. So, more specifically, I’d ideally like to:

(a) be able to narrow the returned value’s type, based on the condition function passed to random_agent
or (b) narrow it later, after it’s been returned as the more generic @multiagent

Does this make sense? I’m new to this :stuck_out_tongue:

Well, I can asnwer (a) easily: since you use @multiagent, all agents are of type MarketActor. Your condtion is correct, you have specified the variant, so the MarketActor you found is of variant Supplier. You cannot narrow the type MarketActor more, as there is no narrower type. That’s how @multiagetn works.

FOr question (b) this is just about how you add an agent of a specific variant to the model? You can extract the variant of an agent with variantof, you have done so already. If so I’d recommend reading again the usage of @multiagent in the tutorial. (Idon’t remmeber the answer)

1 Like

Most of that makes sense to me!

My confusion, still, is: how do I access properties that are specific to a given variant, if I have no way of narrowing it down?

For example, to calculate the minimum price at a given time step, I’d love to do something like:

suppliers = filter(a -> variantof(a) == Supplier, allagents(model))
min_price = reduce((a, b) -> minimum(a.selling_price, b.selling_price))

But I don’t think I can do this, since suppliers is the a more generic typed array, and thus I cannot access its elements’ selling_price property.

Again, I hope this is not completely wrong!

I see. Yes, there is no documentation about this in the tutorial. It kinda assummes that you know the properties of each variant, which you must, since you defined them.

I am not sure if there is an automated way to do what you want to do, perhaps @Tortar knows?

And if you do find such a way please consider contributing it to Agents.jl!

1 Like

I’m not really getting what is the problem, this seems to work fine (on the dev version)?

julia> using Agents

julia> @agent struct Supplier(NoSpaceAgent)
           sellingPrice::Float64 = 1
       end


julia> @agent struct Buyer(NoSpaceAgent)
           supplier::Supplier
       end

julia> @multiagent MarketActor(Buyer, Supplier)

julia> model = StandardABM(MarketActor)
StandardABM with 0 agents of type MarketActor
 agents container: Dict
 space: nothing (no spatial structure)
 scheduler: fastest

julia> supplier = random_agent(model, a -> variantof(a) <: Supplier)

julia> supplier = Supplier(1, 1.)
Supplier(1, 1.0)

julia> add_agent!(MarketActor ∘ Buyer, model; supplier)
MarketActor(Buyer(1, Supplier(1, 1.0)))

Of course! To code? I don’t have the skill for that in Julia… :frowning: But to documentation, yeah :slight_smile:

I don’t understand, exactly.

You just created a new Supplier, overwriting the one returned by random_agent, no?

Without that, I get the type error:

ERROR: MethodError: Cannot `convert` an object of type MarketActor to an object of type Supplier
The function `convert` exists, but no method is defined for this combination of argument types.

EDIT: but your code gave me an idea. It’s not very pretty, but it’s possible to do this:

julia> supplier = random_agent(model, a -> variantof(a) <: Supplier)
julia> supplier = Supplier(id = supplier.id, sellingPrice = supplier.sellingPrice)

Basically, creating a new Supplier struct with the random MarketActor taken. This should be fine, no?

I’m still a bit confused, but I understand that my previous code could be somewhat difficult to interpret, maybe this is what you need:

julia> using Agents


julia> @agent struct Supplier(NoSpaceAgent)
           sellingPrice::Float64 = 1
       end

julia> @agent struct Buyer(NoSpaceAgent)
           supplier::Supplier
       end

julia> @multiagent MarketActor(Buyer, Supplier)

julia> model = StandardABM(MarketActor)
StandardABM with 0 agents of type MarketActor
 agents container: Dict
 space: nothing (no spatial structure)
 scheduler: fastest

julia> add_agent!(MarketActor ∘ Supplier, model; sellingPrice=1.)
MarketActor(Supplier(1, 1.0))

julia> supplier = random_agent(model, a -> variantof(a) <: Supplier)
MarketActor(Supplier(1, 1.0))

julia> add_agent!(MarketActor ∘ Buyer, model; supplier=variant(supplier))
MarketActor(Buyer(2, Supplier(1, 1.0)))

this is the way to add both a Buyer (with a random supplier) and a Supplier to the model, given your definition of agent types.

1 Like

this to me seems like the solution!

Furthermore, to comment:

I think in your specific setup that is a better, and cleaner solution actually. Referencing the ID instead of the agent.