-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Turn HasCell and FixedCell into descriptors #2875
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
base: main
Are you sure you want to change the base?
Conversation
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
|
Performance benchmarks:
|
|
If I'm correct, by shifting from inheritance-based mixins to descriptors, this enables to define agents like this, right? # Before (Inheritance)
class CellAgent(Agent, HasCell, BasicMovement):
# HasCell provides cell property via inheritance
pass# After (Descriptors)
class CellAgent(Agent, BasicMovement):
cell = HasCell() # Descriptor manages cell attributeWould this enable composition-based approaches (as discussed)? # Future possibility: Multiple locations via descriptors
class HybridAgent(Agent):
physical_cell = HasCell()
logical_cell = HasCell()
def step(self):
# Agent can exist in multiple discrete spaces
neighbors_physical = self.physical_cell.get_neighborhood()
neighbors_logical = self.logical_cell.get_neighborhood() |
|
Yes, the code example you have given will work fine with the new descriptor. |
|
I had a look at creating a @property
def position(self) -> np.ndarray:
"""Position of the agent."""
return self.space.agent_positions[self.space._agent_to_index[self]]
def position(self, value: np.ndarray) -> None:
if not self.space.in_bounds(value):
if self.space.torus:
value = self.space.torus_correct(value)
else:
raise ValueError(f"point {value} is outside the bounds of the space")
self.space.agent_positions[self.space._agent_to_index[self]] = valueNote how these properties rely on class HasPosition:
def __get__(self.obj: Agent, type=None)
"""Position of the agent."""
return obj.space.agent_positions[obj.space._agent_to_index[self]]
def __set__(self, obj: Agent, value: np.ndarray)
if not obj.space.in_bounds(value):
if obj.space.torus:
value = obj.space.torus_correct(value)
else:
raise ValueError(f"point {value} is outside the bounds of the space")
obj.space.agent_positions[self.space._agent_to_index[self]] = value
Note how we are using class HasPosition:
def __init__(self, space_attribute_name:stre):
self.space_attribute_name = space_attribute_name
def __get__(self.obj: Agent, type=None)
"""Position of the agent."""
space = getattr(obj, self.space_attribute_name)
return space.agent_positions[space._agent_to_index[self]]
def __set__(self, obj: Agent, value: np.ndarray)
space = getattr(obj, self.space_attribute_name)
if not space.in_bounds(value):
if space.torus:
value = space.torus_correct(value)
else:
raise ValueError(f"point {value} is outside the bounds of the space")
space.agent_positions[self.space._agent_to_index[self]] = value
# we use this accordingly
def MyAgent(Agent)
my_location = HasPosition("my_space")
def __init__(self, model):
super().__init__(model)
self.my_space = model.spaceWe now pass the name of the space attribute as a string to def MyAgent(Agent)
my_location = HasPosition("my_space")
my_other_location = HasPosition("my_other_space")
def __init__(self, model):
super().__init__(model)
self.my_space = model.space_1
self.my_other_space = model.space_2The drawback of this is the fact that it's up to the user to ensure that the string and the attribute name in the init match. Another drawback is that it's not easy to do neighborhood stuff in this way. All of this will have to be defined at the agent level. So an alternative solution is to introduce a new def MyAgent(Agent)
my_location = HasPosition()
def __init__(self, mode, position:np.ndarrayl):
super().__init__(model)
self.my_location = Location(position, space=model.space)I haven't fully fleshed out the details of this new self.location += [0.1, 0.1]
self.location *= 2
self.location = self.location + time_delta * velocity
self.location.get_neighbors_in_radius(5)
self.location.get_nearest_neighbors()So my 2 questions:
|
|
I think we're converging towards the same ideas. I don't like the string to specify the space attribute, but I think it's necessary evil. You have to link them somehow. It would be ideal though, if one location could be linked to multiple spaces. A magic hack we could (I'm not saying should!) do, is defaulting to I don't understand the the |
A draft and WIP PR exploring ideas discussed here
This PR only turns
HasCell andFixedCellinto descriptors and changes nothing else.