Skip to content

Add agent state management system with discrete & continuous states #2547

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
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
156 changes: 156 additions & 0 deletions mesa/experimental/states.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"""State management system for Mesa agents.

This module provides a flexible state management system for Mesa agents, supporting
both discrete and continuous state changes. It enables agents to maintain multiple
states that can change either explicitly (discrete) or based on time (continuous),
and includes support for composite states derived from other states.

Core Classes:
State: Base class defining the state interface
DiscreteState: States with explicit value changes
ContinuousState: States that change over time
CompositeState: States computed from other states
StateAgent: Mesa Agent subclass with state management
"""

from collections.abc import Callable
from typing import Any

Check warning on line 17 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L16-L17

Added lines #L16 - L17 were not covered by tests

from mesa import Agent

Check warning on line 19 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L19

Added line #L19 was not covered by tests


class State:

Check warning on line 22 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L22

Added line #L22 was not covered by tests
"""Base class for all states."""

def __init__(self, initial_value: Any):

Check warning on line 25 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L25

Added line #L25 was not covered by tests
"""Create a new state."""
self._value = initial_value
self._last_update_time = 0
self.model = None # Set when state is added to agent

Check warning on line 29 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L27-L29

Added lines #L27 - L29 were not covered by tests

@property
def value(self) -> Any:

Check warning on line 32 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L31-L32

Added lines #L31 - L32 were not covered by tests
"""Get current state value."""
raise NotImplementedError

Check warning on line 34 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L34

Added line #L34 was not covered by tests

def update(self, time: float) -> None:

Check warning on line 36 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L36

Added line #L36 was not covered by tests
"""Update state to current time."""
raise NotImplementedError

Check warning on line 38 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L38

Added line #L38 was not covered by tests


class DiscreteState(State):

Check warning on line 41 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L41

Added line #L41 was not covered by tests
"""A state with discrete values that change explicitly."""

@property
def value(self) -> Any:

Check warning on line 45 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L44-L45

Added lines #L44 - L45 were not covered by tests
"""Get the current state value."""
return self._value

Check warning on line 47 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L47

Added line #L47 was not covered by tests

@value.setter
def value(self, new_value: Any) -> None:

Check warning on line 50 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L49-L50

Added lines #L49 - L50 were not covered by tests
"""Set the state value."""
self._value = new_value

Check warning on line 52 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L52

Added line #L52 was not covered by tests

def update(self, time: float) -> None:

Check warning on line 54 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L54

Added line #L54 was not covered by tests
"""DiscreteStates only update when value is explicitly changed."""


class ContinuousState(State):

Check warning on line 58 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L58

Added line #L58 was not covered by tests
"""A state that changes continuously over time."""

def __init__(

Check warning on line 61 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L61

Added line #L61 was not covered by tests
self,
initial_value: float,
rate_function: Callable[[float, float], float],
):
"""Create a new continuous state."""
super().__init__(initial_value)
self.rate_function = rate_function

Check warning on line 68 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L67-L68

Added lines #L67 - L68 were not covered by tests

@property
def value(self) -> float:

Check warning on line 71 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L70-L71

Added lines #L70 - L71 were not covered by tests
"""Calculate and return current value based on elapsed time."""
current_time = self.model.time

Check warning on line 73 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L73

Added line #L73 was not covered by tests
if current_time > self._last_update_time:
self.update(current_time)
return self._value

Check warning on line 76 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L75-L76

Added lines #L75 - L76 were not covered by tests

def update(self, time: float) -> None:

Check warning on line 78 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L78

Added line #L78 was not covered by tests
"""Update state value based on elapsed time."""
elapsed = time - self._last_update_time
self._value += self.rate_function(self._value, elapsed)
self._last_update_time = time

Check warning on line 82 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L80-L82

Added lines #L80 - L82 were not covered by tests


class CompositeState(State):

Check warning on line 85 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L85

Added line #L85 was not covered by tests
"""A state derived from other states."""

def __init__(

Check warning on line 88 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L88

Added line #L88 was not covered by tests
self,
dependent_states: list[State],
computation_function: Callable[..., Any],
):
"""Create a new composite state."""
self.dependent_states = dependent_states
self.computation_function = computation_function
super().__init__(None) # Value computed on first access

Check warning on line 96 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L94-L96

Added lines #L94 - L96 were not covered by tests

@property
def value(self) -> Any:

Check warning on line 99 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L98-L99

Added lines #L98 - L99 were not covered by tests
"""Compute value based on dependent states."""
state_values = [state.value for state in self.dependent_states]
return self.computation_function(*state_values)

Check warning on line 102 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L101-L102

Added lines #L101 - L102 were not covered by tests

def update(self, time: float) -> None:

Check warning on line 104 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L104

Added line #L104 was not covered by tests
"""Update all dependent states."""
for state in self.dependent_states:
state.update(time)

Check warning on line 107 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L107

Added line #L107 was not covered by tests


class StateAgent(Agent):

Check warning on line 110 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L110

Added line #L110 was not covered by tests
"""An agent with integrated state management that allows direct attribute-based state access."""

def __init__(self, model):

Check warning on line 113 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L113

Added line #L113 was not covered by tests
"""Create a new agent with state management."""
super().__init__(model)
object.__setattr__(

Check warning on line 116 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L115-L116

Added lines #L115 - L116 were not covered by tests
self, "states", {}
) # Use object.__setattr__ to avoid recursion

def update_states(self) -> None:

Check warning on line 120 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L120

Added line #L120 was not covered by tests
"""Update all states to current time."""
for state in self.states.values():
state.update(self.model.time)

Check warning on line 123 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L123

Added line #L123 was not covered by tests

def __getattribute__(self, name: str) -> Any:

Check warning on line 125 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L125

Added line #L125 was not covered by tests
"""Get an attribute, routing state access to its value."""
states = object.__getattribute__(self, "states")

Check warning on line 127 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L127

Added line #L127 was not covered by tests
if name in states:
# If it's a known state, ensure it is updated before returning its value
state = states[name]
current_time = object.__getattribute__(self.model, "time")

Check warning on line 131 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L130-L131

Added lines #L130 - L131 were not covered by tests
if current_time > state._last_update_time:
state.update(current_time)
return state.value

Check warning on line 134 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L133-L134

Added lines #L133 - L134 were not covered by tests
else:
# Otherwise, return the attribute normally
return object.__getattribute__(self, name)

Check warning on line 137 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L137

Added line #L137 was not covered by tests

def __setattr__(self, name: str, value: Any) -> None:

Check warning on line 139 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L139

Added line #L139 was not covered by tests
"""Set an attribute, allowing direct state assignment or updates."""
states = object.__getattribute__(self, "states")

Check warning on line 141 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L141

Added line #L141 was not covered by tests
# If setting a State object, add or update the states dictionary
if isinstance(value, State):
states[name] = value
value.model = self.model

Check warning on line 145 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L144-L145

Added lines #L144 - L145 were not covered by tests
else:
# If we're setting a non-state value and a corresponding state exists
if name in states:
# The state must be discrete to allow direct setting
if isinstance(states[name], DiscreteState):
states[name].value = value

Check warning on line 151 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L151

Added line #L151 was not covered by tests
else:
raise ValueError("Cannot directly set value of non-discrete state")

Check warning on line 153 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L153

Added line #L153 was not covered by tests
else:
# Otherwise, it's just a normal attribute
object.__setattr__(self, name, value)

Check warning on line 156 in mesa/experimental/states.py

View check run for this annotation

Codecov / codecov/patch

mesa/experimental/states.py#L156

Added line #L156 was not covered by tests
Loading