Skip to content

Commit 7819c53

Browse files
Update space.py
1 parent eec5ed6 commit 7819c53

File tree

1 file changed

+70
-43
lines changed

1 file changed

+70
-43
lines changed

mesa_frames/abstract/space.py

Lines changed: 70 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,13 +1061,21 @@ def move_to(
10611061
randomized order to break ties. The final position of each agent is then
10621062
updated in-place.
10631063
1064+
In practice, this method leverages Polars for efficient DataFrame operations.
1065+
For very large models or specialized data structures (like ``AgentContainer``),
1066+
consider referencing the minimal UDP approach in
1067+
``space/utils/spatial_utils/grid_pairs.py``. You can adapt similar logic
1068+
(e.g., using lazy frames or partial computations) to maintain performance
1069+
at scale. If you observe performance degradation for large neighborhoods,
1070+
you may need to refactor or optimize further.
1071+
10641072
Parameters
10651073
----------
10661074
agents : AgentLike
10671075
A DataFrame-like structure containing agent information. Must include
10681076
at least the following columns:
1069-
- ``agent_id``: a unique identifier for each agent
1070-
- ``dim_0``, ``dim_1``: the current positions of agents
1077+
- ``unique_id``: a unique identifier for each agent
1078+
- ``dim_0``, ``dim_1``: the current positions of agents (in ``agents.pos``)
10711079
- Optionally ``vision`` if ``radius`` is not provided
10721080
attr_names : str or list of str
10731081
The name(s) of the attribute(s) used for ranking the neighborhood cells.
@@ -1079,7 +1087,8 @@ def move_to(
10791087
- ``"min"`` for ascending order
10801088
10811089
If a single string is provided, it is applied to all attributes in
1082-
``attr_names``.
1090+
``attr_names``. **Note**: this method strongly assumes that the length
1091+
of ``attr_names`` matches the length of ``rank_order``.
10831092
radius : int or pl.Series, optional
10841093
The radius (or per-agent radii) defining the neighborhood around agents.
10851094
If not provided, this method attempts to use the ``vision`` column from
@@ -1095,7 +1104,52 @@ def move_to(
10951104
-------
10961105
None
10971106
This method updates agent positions in-place based on the computed best moves.
1107+
1108+
Raises
1109+
------
1110+
ValueError
1111+
If the lengths of ``attr_names`` and ``rank_order`` do not match, or if
1112+
``radius`` is not provided and the ``agents`` DataFrame does not contain
1113+
a ``vision`` attribute.
1114+
1115+
Notes
1116+
-----
1117+
- Type definitions (e.g., ``AgentLike``) are typically maintained in
1118+
``mesa_frames.types``.
1119+
- For advanced usage or adapting this method to a custom data structure
1120+
(e.g., an ``AgentContainer``), you may reference the logic in
1121+
``space/utils/spatial_utils/grid_pairs.py`` for a minimal UDP approach.
1122+
Using lazy frames or partial computations might help if performance
1123+
scales poorly with large data sets.
1124+
1125+
This method performs the following steps:
1126+
1. Compute the neighborhood for each agent using ``self.get_neighborhood``.
1127+
2. Join the neighborhood data with cell information (``self.cells``) and agent
1128+
positions (from ``agents.pos``).
1129+
3. Optionally shuffle the agent order to break ties.
1130+
4. Sort the neighborhood cells by the specified attribute(s) and order(s).
1131+
5. Iteratively select the best moves, ensuring no cell is claimed by multiple agents.
1132+
6. Call ``self.move_agents`` to finalize the updated positions of each agent.
1133+
1134+
Examples
1135+
--------
1136+
>>> # Assume we have a DataFrame 'agents' with columns:
1137+
>>> # ['unique_id', 'dim_0', 'dim_1', 'vision', 'food_availability', 'safety_score']
1138+
>>> # and a space object 'space' in a mesa-frames model.
1139+
>>>
1140+
>>> # We want to move each agent to the best available cell within its vision
1141+
>>> # radius, prioritizing cells with higher 'food_availability' and 'safety_score'.
1142+
>>> space.move_to(
1143+
... agents=agents,
1144+
... attr_names=["food_availability", "safety_score"],
1145+
... rank_order=["max", "max"], # rank both attributes in descending order
1146+
... radius=None, # use each agent's 'vision' column
1147+
... include_center=False, # do not include the agent's current cell
1148+
... shuffle=True # randomize the order in which agents move
1149+
... )
1150+
>>> # After this call, each agent's position in 'agents' will be updated in-place.
10981151
"""
1152+
10991153
# Ensure attr_names and rank_order are lists of the same length
11001154
if isinstance(attr_names, str):
11011155
attr_names = [attr_names]
@@ -1121,18 +1175,13 @@ def move_to(
11211175
)
11221176
neighborhood = neighborhood.join(self.cells, on=["dim_0", "dim_1"])
11231177

1124-
# Determine the agent identifier column
1125-
agent_id_col = "agent_id" if "agent_id" in agents.columns else "unique_id"
1126-
1127-
# Add a column to identify the center agent
1128-
join_result = neighborhood.join(
1129-
agents.select(["dim_0", "dim_1", agent_id_col]),
1130-
left_on=["dim_0_center", "dim_1_center"],
1131-
right_on=["dim_0", "dim_1"]
1132-
)
1133-
1178+
# Add a column to identify the center agent (the one evaluating moves)
11341179
neighborhood = neighborhood.with_columns(
1135-
agent_id_center=join_result[agent_id_col]
1180+
agent_id_center=neighborhood.join(
1181+
agents.pos,
1182+
left_on=["dim_0_center", "dim_1_center"],
1183+
right_on=["dim_0", "dim_1"],
1184+
)["unique_id"]
11361185
)
11371186

11381187
# Determine the processing order of agents
@@ -1180,43 +1229,30 @@ def move_to(
11801229

11811230
# Iteratively select the best moves
11821231
best_moves = pl.DataFrame()
1183-
max_iterations = min(len(agents) * 2, 1000) # Safeguard against infinite loops
1184-
iteration_count = 0
1185-
1186-
while len(best_moves) < len(agents) and iteration_count < max_iterations:
1187-
iteration_count += 1
1188-
1232+
while len(best_moves) < len(agents):
11891233
# Count how many times each (dim_0, dim_1) is being claimed
11901234
neighborhood = neighborhood.with_columns(
11911235
priority=pl.col("agent_order").cum_count().over(["dim_0", "dim_1"])
11921236
)
1193-
11941237
new_best_moves = (
11951238
neighborhood.group_by("agent_id_center", maintain_order=True)
11961239
.first()
11971240
.unique(subset=["dim_0", "dim_1"], keep="first", maintain_order=True)
11981241
)
1199-
12001242
condition = (
12011243
pl.col("blocking_agent_id").is_null()
12021244
| (pl.col("blocking_agent_id") == pl.col("agent_id_center"))
12031245
)
1204-
12051246
if len(best_moves) > 0:
12061247
condition = condition | pl.col("blocking_agent_id").is_in(
12071248
best_moves["agent_id_center"]
12081249
)
1209-
12101250
condition = condition & (pl.col("priority") == 1)
12111251
new_best_moves = new_best_moves.filter(condition)
1212-
12131252
if len(new_best_moves) == 0:
12141253
break
1215-
1254+
12161255
best_moves = pl.concat([best_moves, new_best_moves])
1217-
1218-
# Update neighborhood to exclude agents that already have a move
1219-
# and cells that are already claimed
12201256
neighborhood = neighborhood.filter(
12211257
~pl.col("agent_id_center").is_in(best_moves["agent_id_center"])
12221258
)
@@ -1226,20 +1262,11 @@ def move_to(
12261262

12271263
# Move agents to their new positions
12281264
if len(best_moves) > 0:
1229-
try:
1230-
self.move_agents(
1231-
best_moves.sort("agent_order")["agent_id_center"],
1232-
best_moves.sort("agent_order").select(["dim_0", "dim_1"])
1233-
)
1234-
except Exception as e:
1235-
# Check if the agent exists in the model
1236-
available_agents = set(self.model.agents[agent_id_col].to_list()) if hasattr(self.model, 'agents') else set()
1237-
missing_agents = [a for a in best_moves["agent_id_center"].to_list() if a not in available_agents]
1238-
1239-
if missing_agents and available_agents:
1240-
raise ValueError(f"Some agents are not present in the model: {missing_agents}")
1241-
else:
1242-
raise ValueError(f"Error moving agents: {e}")
1265+
self.move_agents(
1266+
best_moves.sort("agent_order")["agent_id_center"],
1267+
best_moves.sort("agent_order").select(["dim_0", "dim_1"])
1268+
)
1269+
12431270

12441271

12451272

0 commit comments

Comments
 (0)