Encapsulate cell movement in properties#2333
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
|
I think I like this, it makes it much more explicit what's happening. It's like modifying One thing I'm curious about if I will review in more detail tomorrow! |
|
Ha! Given enough time I think @quaquel will turn every mesa behaviour into a descriptor 😂 No, I really like this and this somehow put me over the edge to finally try to fully grok descriptors. Let me write in more detail when I have more time, but I think using just a property with a setter might be better |
A space does not need to know anything about Agents. A space has cells. A cell can have agents. Agents and cells need to be aware of each other. A space is primarily a container for cells, nothing more. Multiple spaces should not be a problem. You just assign another cell to the attribute, and you're done.
The shortest summary is "reusable properties".
I think you need a getter because the agent needs to know its cell for e.g., a random walk, but curious to see what more you have to say in detail. Given the fact that both @Corvince and @EwoutH like this idea, I'll try to update the tests asap to make sure this is fully covered again. |
|
(I just looked at it from my phone, still need to do a thorough review) |
My main objection would be that it might make some things easier if we have a clear "cell" definition. That is with a reusable descriptor we have the advantage of allowing several "cell" descriptor from several spaces. In practice I think this isn't very common. In the case of a "normal" property we could have the following: class HasCell:
_cell: Cell | None = None
@property
def cell(self):
return self._cell
@cell.setter
def cell(self, cell: Cell):
... # Your logic from CellDescriptor.__set__And then we can have separate movement mixin class CellMovement:
def move_to(self, cell: Cell):
self.cell = cell
... # More movement functionsAnd finally the CellAgent: class CellAgent(Agent, HasCell, CellMovement):
....In this case its unambigious and easy. We know we have to assign to "cell". If the cell descriptor can be assigned to any name, how would we know the right name? Further advantages are that one could theoretically overwrite the |
Not sure I fully understand your point, but a descriptor has the Also, the I like your name |
|
Sorry if I wasn't clear. The movement mixin was just an example (although I would like to have the clearer move_to API, even if redundant. But this is irrelevant for my point here). The point is that for any mixin or rather anything wanting to operate on the cell property it needs to know the name. Of course the user knows the name, but our framework doesn't. So in the example move_to method the line |
Ok, that makes sense. If we want to add any additional functionality like the MoveTo example, then yes we need to lock down the name of the attribute to which the current cell is assigned. |
|
A quick follow-up to @Corvince point about the naming of the attribute. There is, in fact, already a case like this. It is up to the user to actively remove an agent from the cell before calling This leaves the other idea: instead of having a Of course this still leaves room for more sophisticated movement methods that for example use a heading or something. |
|
One thing I would find interesting is how this would work with multiple grids. For example, I currently have a model in which Agents move both in fine grained areas (postal code “cells”) and which are part of larger scale municipalities. So here you have like a nested spaces with two resolutions. Another case would be to completely orthogonal spaces, for example a space representing some physical representation and another representing social, economic or other relations (maybe a cell space isn’t suitable for that). CC @wang-boyu (I’m especially curious how you see this (and the whole cell space) integrating with Mesa-Geo in the long term) |
|
|
Just making sure we don’t box ourselves in / prohibit a use case unintentionally. |
|
Yes, I see this as a perfect base case for the ideas discussed in #2292. After this is merged I would update #2292 to incorporate the named coordinates and create a "Movement" mixin class. Then CellAgent can become just a class inherting from Agent, HasCell and Movement without any MRO related headaches (because of those, only Agent has an init method). I think this will come out nicely, but once implemented we can see how it plays out by adapting some examples. |
|
Performance benchmarks:
|
|
Performance benchmarks:
|
This comment was marked as duplicate.
This comment was marked as duplicate.
|
There are 6 ruff errors otherwise this looks good to go from my side (but a lot of things are based on my ideas so I would like another reviewer to approve) |
|
Disclaimer: I haven't read #2292. I'm not fully sure why having a While @quaquel 's stance on accommodating LLM can be found at #2237 (comment), I find that making it less likely for LLM to fall into a gotcha / to help it to reason better actually doubles as to help human reasoners as well. Why can't we have both |
The idea is that |
| # If unhappy, move: | ||
| if similar < self.homophily: | ||
| self.move_to(self.model.grid.select_random_empty_cell()) | ||
| self.cell = self.model.grid.select_random_empty_cell() |
There was a problem hiding this comment.
If we have both, then why is this not self.move_to?
There was a problem hiding this comment.
please read the discussion both here and in #2292.
There was a problem hiding this comment.
This doesn't sound inclusive to me. This was a genuine question, yet I was told to comb through the wall of text to get my answer. The 1 approval from a third position to merge is wearing me down, when a feedback from someone else not biased with the design choice was asked.
There was a problem hiding this comment.
I am sorry for the short answer and the merge, but move_to is added back in via #2292.
There was a problem hiding this comment.
I see, then you have my post-merge approval.
EwoutH
left a comment
There was a problem hiding this comment.
Thanks for working on this. This looks really robust, and it looks like it's more robust than previous movement methods.
I left a few thoughts when going through the diff.
| def step(self): | ||
| """One step of the agent.""" | ||
| self.random_move() | ||
| self.cell = self.cell.neighborhood.select_random_cell() |
There was a problem hiding this comment.
Nice to have this both an one-liner and in a single location!
There was a problem hiding this comment.
Yes I like the shortness of it now. Where in mesa 2.1 or so, you needed a dedicated random walker class (see here to now having it as a oneliner.
| # TODO:: fully grown can just be an int --> so one less param (i.e. countdown) | ||
| super().__init__(model) | ||
| self._fully_grown = fully_grown | ||
| self._fully_grown = True if countdown == 0 else False # Noqa: SIM210 |
There was a problem hiding this comment.
An comment explaining this might be useful.
There was a problem hiding this comment.
Oh I might better have left that change out. Basically, there was a fixme statement in the docstring to go from 2 arguments (countdown value and whether or not being fully grown) to only 1. If countdown is 0, you are fully grown, otherwise you are not.
| assert agent not in cell2.agents | ||
|
|
||
| agent.remove() | ||
| assert agent not in model._all_agents |
There was a problem hiding this comment.
Completely unrelated, doesn't agent not in model.agents work? If so, we might want to implementent some construct so that it does.
There was a problem hiding this comment.
In this case I explicitly am testing for the hard reference because if that is gone, I know that the object can be garbage collected.
The syntax in general is indeed valid.
| cell.add_agent(self) | ||
|
|
||
|
|
||
| class CellAgent(Agent, HasCell): |
There was a problem hiding this comment.
Now that this Agent is moving, this name might get confusing with an Agent that sits in a fixed place, and the whole grid is covered with such (like the patches in NetLogo). For now fine, but maybe something we should keep in mind as the cell space develops.
This PR encapsulates agent movement between cells using a
HasCellmixin with property descriptors. It replaces the explicitmove_tomethod with cell attribute assignment.Motive
The goal is to simplify the API for agent movement in cell-based spaces and lay the groundwork for more flexible agent behaviors. This approach allows movement to be handled through simple attribute assignment rather than explicit method calls.
Implementation
HasCellmixin class that implements cell assignment logic using property descriptorsCellAgentto use theHasCellmixinmove_tomethod fromCellAgentCellAgent.removeto handle cell removal when an agent is removedUsage Examples
Previous approach:
New approach:
In the Schelling model:
Python Constructs and Patterns Used
HasCellmixin uses the@propertydecorator and a setter to create a managed attribute for the cell. This allows for custom logic to be executed when the cell is accessed or modified.HasCellclass is designed as a mixin, allowing its functionality to be easily combined with other classes through multiple inheritance.Additional Notes
move_tomethod may be reintroduced with enhanced functionality in a future PR