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 agent state management system with discrete & continuous states #2547

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

EwoutH
Copy link
Member

@EwoutH EwoutH commented Dec 13, 2024

This PR builds on discussion #2529.

Summary

This PR adds a new state management framework to Mesa, enabling both discrete and continuous state management in agents. It provides a clean, minimal API for managing agent states that can change both discretely (at specific moments) and continuously (over time), as well as composed states that derive their values from other states.

Motive

Agent-based models often need to track various agent states that change in different ways. While discrete state changes are straightforward to implement, continuous changes (like energy depletion, resource growth) are more challenging to model efficiently. Current approaches often require manual calculations each timestep or complex event scheduling.

This framework addresses several common challenges:

  1. Efficient handling of continuously changing values
  2. Clean separation of state logic from agent behavior
  3. Support for derived states that depend on other states
  4. Automatic time-based updates only when needed

Implementation

The implementation adds four new classes:

  1. State: Base class defining the state interface
  2. DiscreteState: For states that change at specific moments
  3. ContinuousState: For states that change continuously over time
  4. CompositeState: For states derived from other states

And a mixin class:

  • StateAgent: Extension of Mesa's Agent class with state management capabilities

Key implementation details:

  • States are updated only when their values are accessed
  • Continuous states use rate functions to calculate changes
  • Composite states automatically update their dependencies
  • Clean integration with Mesa's existing time management
  • Direct state access via attributes, e.g., agent.some_state.
  • Type-safe state modifications

Usage Examples

Here are the key API examples:

# Assigning states to an agent
agent.energy = ContinuousState(100, lambda c, t: -0.1 * t)
agent.status = DiscreteState("searching")
agent.health = CompositeState([agent.energy], lambda energy: "healthy" if energy > 50 else "weak")

# Accessing states
print(agent.energy)   # Automatically fetches the updated value
agent.status = "hunting"  # Directly updates the discrete state

# Update all states to current time (also happens automatically on access)
agent.update_states()                                                            

Here are three small, complete examples showing different use cases:

# Example 1: Simple Predator-Prey Agent
class Wolf(StateAgent):
    def __init__(self, model):
        super().__init__(model)
        self.energy = ContinuousState(100, lambda c, t: -0.1 * t)
        self.status = DiscreteState("searching")

    def step(self):
        if self.energy < 30:
            self.status = "hunting"
# Example 2: Growing Plant with Multiple States
class Plant(StateAgent):
    def __init__(self, model):
        super().__init__(model)
        self.height = ContinuousState(1, lambda c, t: 0.1 * t * (100 - c))
        self.water = ContinuousState(100, lambda c, t: -0.2 * t)
        self.status = CompositeState([self.water], lambda w: "healthy" if w > 20 else "wilting")
# Example 3: Social Agent with Multiple Interacting States
class SocialAgent(StateAgent):
    def __init__(self, model):
        super().__init__(model)
        self.happiness = ContinuousState(50, lambda c, t: -0.05 * t)
        self.energy = ContinuousState(100, lambda c, t: -0.2 * t if self.happiness < 30 else -0.1 * t)
        self.mood = CompositeState(
            [self.happiness, self.energy],
            lambda h, e: "excellent" if h > 70 and e > 70 else "good" if h > 40 else "poor"
        )

Additional Notes

Future Enhancements:

  1. Adding state history tracking
  2. Supporting threshold events
  3. Adding more specialized state types
  4. Adding batch state updates
  5. Supporting stochastic state changes

Related Work

To review

…ates

Adds a minimal state management system to Mesa that supports:
- Discrete states with explicit value changes
- Continuous states that update based on elapsed time
- Composite states derived from other states
- Integration with Mesa's Agent class via StateAgent

This provides a clean API for managing agent states in simulations while
handling both discrete events and continuous changes efficiently.
@EwoutH EwoutH added feature Release notes label experimental Release notes label labels Dec 13, 2024

This comment was marked as off-topic.

@EwoutH
Copy link
Member Author

EwoutH commented Dec 13, 2024

I updated the PR to:

  • Allow directe attribute access (agent.some_state)
  • Remove the now unneeded naming construct
  • Added a check that states are up-to-date before fetching them

The API is now much cleaner!

@Corvince
Copy link
Contributor

I haven't seen the discussion and haven't looked at the implementation yet, but this reads fantastic! The API seems simple and powerful. Really looking forward to how this evolved, but congratulations already!

@EwoutH
Copy link
Member Author

EwoutH commented Dec 13, 2024

Thanks! Only thing I'm a bit worried about is performance.

Don't make things states that don't need to be a state I guess.

@EwoutH
Copy link
Member Author

EwoutH commented Dec 13, 2024

The next thing is that I would like to do is implement triggers for state changes:

  • A DiscreteState can trigger on every change, or to/from a certain state
  • A ContinuousState can trigger on reaching (below / above) a threshold value

These thresholds can be read (Boolean value) but could also emit an event to an event scheduler.

This would tightly integrate with potentially Mesa Observables and DEVS, so we need a bit of coordination on this.

@EwoutH
Copy link
Member Author

EwoutH commented Dec 13, 2024

I also looked a bit into how it could integrate with Mesa Observables. I have a rough Proof of Concept here:

@quaquel I would love to hear your take on it.

@quaquel
Copy link
Member

quaquel commented Dec 14, 2024

There is a lot going on here. I need time to look at it closely and make up my mind. Given that is experimental, I believe we can push foward regardless.

However, some quick reactions

  1. Playing devil's advocate, state means attribute, so what does this really add?
  2. Playing devil's advocate, explicit is better than implicit. Your Wolf example leaves a lot implicit (i.e., how energy is being updated automagically). Is all this implicitness of updating states desirable?
  3. Regarding signals, this makes it even more complicated. I am not sure you can simply subclass Observable. Observable is a class-level attribute. Related, DerivedState is analogous to Computed. In fact, in javascript, a computed is typically explained as being a derived state, while a signal is considered a state. The complexity in case of MESA is that we want to combine this with objects/classes, while in javascript signals, computed, and effect are used within a functional programming paradigm. I like the idea of making this work with/on top of mesa signals. It would open up reactive ABMs (which currently don't exist at all to my knowledge).

@EwoutH
Copy link
Member Author

EwoutH commented Dec 14, 2024

Thanks for thinking critically about it.

The main advantage is to decouple state updates from the main, fixed timestep, agent logic. Especially if you have a DEVS scheduler, you can now update states continuously, instead of them being literal step functions that get updated only on the whole time steps.

With the thresholds, it also allows events to fire exactly upon reaching a certain state.

It does add complexity and maybe makes some things more implicit/hidden. I would like to explore where this is worth it.

@EwoutH
Copy link
Member Author

EwoutH commented Jan 3, 2025

@projectmesa/maintainers and others, I could use some additional eyes on this.

  1. Is this feature useful?
  2. Conceptually sound?
  3. Implementation good?

And any other suggestions ideas. I mainly need to know if this is an idea worth pushing further, or not.

@jackiekazil
Copy link
Member

jackiekazil commented Jan 4, 2025

I am with @quaquel on all comments, but I want to +1 -- it is experimental - so I am less hesitant to stop integration.

My first concern was -- how this helps or hinders performance .... I saw the comment on this as well. This is something we should evaluate. The question is -- evaluate before or after integration into experimental?

Overall though -- this is potentially very interesting and exciting!

@quaquel
Copy link
Member

quaquel commented Jan 8, 2025

  1. Is this feature useful?

The basic version of purely automatic state updates based on elapsed time does not seem particularly useful because it adds little. I see two possible directions to make it more useful. One option is to tie it to observables and computed. This would open up the possibility of reactive ABMs, which don't exist as far as I know.

The other option just occurred to me but might require some further testing. In short, we might be able to bridge between mesa-frames and normal mesa with dedicated state descriptors. Let me explain: both property layers and the new style continuous space use objects with attributes that are views into an underlying numpy array. That is, the state data is stored not inside the agent attribute but is part of a larger numpy array. What if, in, say, the Boltzmann Wealth Model, agent.wealth would point to a NumPy array containing the wealth of all agents? Then gini calculations and any other aggregate operations would become a lot faster. Moreover, some of the items on your further expansion list became easy (e.g., batch updates to states).

If I have an hour somewhere, I can probably code up a proof of principle of this idea. Let me know if you want it.

  1. Conceptually sound?

As indicated before, I don't necessarily like the implicit nature of state updates as presented here. But if tied to a reactive paradigm, I think this can be overcome.

If we go down option two first (so numpy array based collective state), there is some further design work required. The critical thing to handle carefully is the addition and removal of agents. I found a clever solution with numpy for continuous space (see ContinuousSpace_remove_agent), but it might be possible to get something easier by using polars instead of numpy. This is a hunch based on looking at mesa-frames codebase.

  1. Implementation good?

Given 1 and 2, I think some further work might be required before there is a need to discuss the imlementation details

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
experimental Release notes label feature Release notes label
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants