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

Allow using default values in agents macro #833

Merged
merged 5 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# main

- The `@agents` macro now supports fields with default values.

# v5.17

- New function `replicate!` allows to create a new instance of a given agent at the same position with the possibility to specify some
Expand Down
18 changes: 15 additions & 3 deletions src/core/agents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ as well as any additional ones the user may provide via the `begin` block.
See below for examples.

Using `@agent` is the recommended way to create agent types for Agents.jl,
however keep in mind that the macro (currently) doesn't work with `Base.@kwdef`
or `const` declarations in individual fields (for Julia v1.8+).
however keep in mind that the macro (currently) doesn't work with `const`
declarations in individual fields (for Julia v1.8+).

Structs created with `@agent` by default subtype `AbstractAgent`.
They cannot subtype each other, as all structs created from `@agent` are concrete types
Expand Down Expand Up @@ -91,6 +91,18 @@ mutable struct Baker{T} <: AbstractAgent
breadz_per_day::T
end
```
Notice that you can also use default values for some fields, in this case you
will need to specify the field names with the non-default values
```julia
@agent Person{T} GridAgent{2} begin
age::Int = 30
moneyz::T
end
# default age value
Person(id = 1, pos = (1, 1), moneyz = 2000)
# new age value
Person(1, (1, 1), 40, 2000)
```
### Example with optional hierarchy
An alternative way to make the above structs, that also establishes
a user-specific subtyping hierarchy would be to do:
Expand Down Expand Up @@ -192,7 +204,7 @@ macro agent(new_name, base_type, super_type, extra_fields)
expr = quote
# Also notice that we escape supertype and interpolate it twice
# because this is expected to already be defined in the calling module
mutable struct $name <: $$(esc(super_type))
@kwdef mutable struct $name <: $$(esc(super_type))
$(base_fields...)
$(additional_fields...)
end
Expand Down
7 changes: 7 additions & 0 deletions test/model_creation_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ using Test, Agents, Random
end
@test Fisher <: AbstractHuman
@test :fish_per_day ∈ fieldnames(Fisher)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here definitely more tests should have been added to see how this works with add_agent! that automatically creates agents.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it is actually something I didn't consider when doing the pr :D, it seems that it shouldn't have any effect on the version with args, I'm also pretty sure that currently if we pass anything in kwargs it will throw error (even before this pr)

Copy link
Member Author

@Tortar Tortar Jul 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we can support both ways with:

function add_agent!(
    pos::ValidPos,
    A::Type{<:AbstractAgent},
    model::ABM,
    properties::Vararg{Any, N};
    kwproperties...,
) where {N}
    id = nextid(model)
    if isempty(kwproperties)
        newagent = A(id, pos, properties...)
    else
        newagent = A(; id = id, pos = pos, kwproperties...)
    end
    add_agent_pos!(newagent, model)
end

It should be as fast as before since the branch prediction is pretty easy so the compiler should be able to infer what to do, but we should probably verify it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed I don't see any meaningful difference in our model comparisons

# before this modification
Agents.jl WolfSheep-small (ms): 2.8282849999999997
Agents.jl WolfSheep-large (ms): 99.588342
Agents.jl Flocking-small (ms): 9.377578999999999
Agents.jl Flocking-large (ms): 67.284021
Agents.jl Schelling-small (ms): 0.453293
Agents.jl Schelling-large (ms): 4.035073
Agents.jl ForestFire-small (ms): 0.767832
Agents.jl ForestFire-large (ms): 16.152233

# after this modification
Agents.jl WolfSheep-small (ms): 2.82549
Agents.jl WolfSheep-large (ms): 99.25996699999999
Agents.jl Flocking-small (ms): 8.799429
Agents.jl Flocking-large (ms): 67.878292
Agents.jl Schelling-small (ms): 0.455848
Agents.jl Schelling-large (ms): 4.059518
Agents.jl ForestFire-small (ms): 0.776428
Agents.jl ForestFire-large (ms): 16.248959

agent_kwdef = Agent9(id = 1, f2 = 10)
values = (1, 40, 10, 3.0)
@test all(getfield(agent_kwdef, n) == v for (n, v) in zip(fieldnames(Agent9), values))
agent_kwdef = Agent9(1, 20, 10, 4.0)
values = (1, 20, 10, 4.0)
@test all(getfield(agent_kwdef, n) == v for (n, v) in zip(fieldnames(Agent9), values))
end


Expand Down
6 changes: 6 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ end

Agent8(id, pos; f1, f2) = Agent8(id, pos, f1, f2)

@agent Agent9 NoSpaceAgent begin
f1::Int = 40
f2::Int
f3::Float64 = 3.0
end

@agent SchellingAgent GridAgent{2} begin
mood::Bool
group::Int
Expand Down