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

Add default values and missing value handling to agentset.get #2279

Merged
merged 31 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d90b0f7
further updates
quaquel Jan 21, 2024
9586490
Update benchmarks/WolfSheep/__init__.py
quaquel Jan 21, 2024
4aaa35d
Merge remote-tracking branch 'upstream/main'
quaquel Feb 21, 2024
d31478c
Merge remote-tracking branch 'upstream/main'
quaquel Apr 22, 2024
6e4c72e
Merge remote-tracking branch 'upstream/main'
quaquel Aug 14, 2024
70fbaf5
Merge remote-tracking branch 'upstream/main'
quaquel Aug 18, 2024
724c8db
Merge remote-tracking branch 'upstream/main'
quaquel Aug 21, 2024
45184a4
Merge remote-tracking branch 'upstream/main'
quaquel Aug 22, 2024
3d75d30
Merge remote-tracking branch 'upstream/main'
quaquel Aug 27, 2024
2759244
Update __init__.py
quaquel Aug 27, 2024
fc8aaea
Merge remote-tracking branch 'upstream/main'
quaquel Aug 28, 2024
1ba465d
Merge remote-tracking branch 'upstream/main'
quaquel Aug 30, 2024
2b5e822
Merge remote-tracking branch 'upstream/main'
quaquel Sep 2, 2024
3847799
Merge remote-tracking branch 'upstream/main'
quaquel Sep 4, 2024
301d80e
Merge remote-tracking branch 'upstream/main'
quaquel Sep 4, 2024
fe3d655
Merge remote-tracking branch 'upstream/main'
quaquel Sep 4, 2024
7d18880
Merge remote-tracking branch 'upstream/main'
quaquel Sep 5, 2024
6b49a3b
Merge remote-tracking branch 'upstream/main'
quaquel Sep 5, 2024
2f1cd4d
add a default value to agentset.get
quaquel Sep 4, 2024
d168eb6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 4, 2024
2a84336
make arguments explicit and add skip option
quaquel Sep 5, 2024
8bc05b1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 5, 2024
713373a
ruff related fixes
quaquel Sep 5, 2024
81c4a6d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 5, 2024
721884c
test_agent.py: Add some more cases for get
EwoutH Sep 5, 2024
73d9346
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 5, 2024
bd20fa6
Update mesa/agent.py
quaquel Sep 5, 2024
507e252
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 5, 2024
576a389
Update get bug and tests
EwoutH Sep 6, 2024
b623cd1
remove skip and update tests
quaquel Sep 6, 2024
9d33f68
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 6, 2024
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
55 changes: 42 additions & 13 deletions mesa/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from random import Random

# mypy
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Literal

if TYPE_CHECKING:
# We ensure that these are not imported during runtime to prevent cyclic
Expand Down Expand Up @@ -348,29 +348,58 @@ def agg(self, attribute: str, func: Callable) -> Any:
values = self.get(attribute)
return func(values)

def get(self, attr_names: str | list[str]) -> list[Any]:
def get(
self,
attr_names: str | list[str],
handle_missing: Literal["error", "default"] = "error",
default_value: Any = None,
) -> list[Any] | list[list[Any]]:
"""
Retrieve the specified attribute(s) from each agent in the AgentSet.

Args:
attr_names (str | list[str]): The name(s) of the attribute(s) to retrieve from each agent.
handle_missing (str, optional): How to handle missing attributes. Can be:
- 'error' (default): raises an AttributeError if attribute is missing.
- 'default': returns the specified default_value.
default_value (Any, optional): The default value to return if 'handle_missing' is set to 'default'
and the agent does not have the attribute.

Returns:
list[Any]: A list with the attribute value for each agent in the set if attr_names is a str
list[list[Any]]: A list with a list of attribute values for each agent in the set if attr_names is a list of str
list[Any]: A list with the attribute value for each agent if attr_names is a str.
list[list[Any]]: A list with a lists of attribute values for each agent if attr_names is a list of str.

Raises:
AttributeError if an agent does not have the specified attribute(s)

"""
AttributeError: If 'handle_missing' is 'error' and the agent does not have the specified attribute(s).
ValueError: If an unknown 'handle_missing' option is provided.
"""
is_single_attr = isinstance(attr_names, str)

if handle_missing == "error":
if is_single_attr:
return [getattr(agent, attr_names) for agent in self._agents]
else:
return [
[getattr(agent, attr) for attr in attr_names]
for agent in self._agents
]

elif handle_missing == "default":
if is_single_attr:
return [
getattr(agent, attr_names, default_value) for agent in self._agents
]
else:
return [
[getattr(agent, attr, default_value) for attr in attr_names]
for agent in self._agents
]

if isinstance(attr_names, str):
return [getattr(agent, attr_names) for agent in self._agents]
else:
return [
[getattr(agent, attr_name) for attr_name in attr_names]
for agent in self._agents
]
raise ValueError(
f"Unknown handle_missing option: {handle_missing}, "
"should be one of 'error' or 'default'"
)

def set(self, attr_name: str, value: Any) -> AgentSet:
"""
Expand Down
45 changes: 45 additions & 0 deletions tests/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,51 @@ def remove_function(agent):
assert len(agentset) == 0


def test_agentset_get():
model = Model()
_ = [TestAgent(i, model) for i in range(10)]

agentset = model.agents

agentset.set("a", 5)
agentset.set("b", 6)

# Case 1: Normal retrieval of existing attributes
values = agentset.get(["a", "b"])
assert all((a == 5) & (b == 6) for a, b in values)

# Case 2: Raise AttributeError when attribute doesn't exist
with pytest.raises(AttributeError):
agentset.get("unknown_attribute")

# Case 3: Use default value when attribute is missing
results = agentset.get(
"unknown_attribute", handle_missing="default", default_value=True
)
assert all(results) is True

# Case 4: Retrieve mixed attributes with default value for missing ones
values = agentset.get(
["a", "unknown_attribute"], handle_missing="default", default_value=True
)
assert all((a == 5) & (unknown is True) for a, unknown in values)

# Case 5: Invalid handle_missing value raises ValueError
with pytest.raises(ValueError):
agentset.get("unknown_attribute", handle_missing="some nonsense value")

# Case 6: Retrieve multiple attributes with mixed existence and 'default' handling
values = agentset.get(
["a", "b", "unknown_attribute"], handle_missing="default", default_value=0
)
assert all((a == 5) & (b == 6) & (unknown == 0) for a, b, unknown in values)

# Case 7: 'default' handling when one attribute is completely missing from some agents
agentset.select(at_most=0.5).set("c", 8) # Only some agents have attribute 'c'
values = agentset.get(["a", "c"], handle_missing="default", default_value=-1)
assert all((a == 5) & (c in [8, -1]) for a, c in values)


def test_agentset_agg():
model = Model()
agents = [TestAgent(model) for i in range(10)]
Expand Down
Loading