phyelds-mesa 8.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: phyelds-mesa
|
|
3
|
+
Version: 8.1.0
|
|
4
|
+
Summary: Mesa integration for phyelds: aggregate programs on the agents of a Mesa model
|
|
5
|
+
Home-page: https://github.com/phyelds/phyelds
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Author: Davide Domini
|
|
8
|
+
Author-email: davide.domini@unibo.it
|
|
9
|
+
Requires-Python: >=3.12,<3.14
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Dist: mesa[network] (>=3.5.1,<4.0.0)
|
|
16
|
+
Requires-Dist: phyelds (>=8.0.0,<9.0.0)
|
|
17
|
+
Project-URL: Repository, https://github.com/phyelds/phyelds
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# phyelds-mesa
|
|
21
|
+
|
|
22
|
+
[Mesa](https://mesa.readthedocs.io) integration for [phyelds](https://pypi.org/project/phyelds/):
|
|
23
|
+
run aggregate programs on the agents of a Mesa model.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install phyelds-mesa
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`phyelds-mesa` installs a compatible `phyelds` release and Mesa.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from phyelds.calculus import aggregate
|
|
37
|
+
from phyelds.libraries.device import sense
|
|
38
|
+
from phyelds.libraries.distances import hops_distance
|
|
39
|
+
from phyelds.libraries.spreading import distance_to
|
|
40
|
+
from phyelds.mesa import AggregateRuntime, within
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@aggregate
|
|
44
|
+
def hops_to_source():
|
|
45
|
+
return distance_to(sense("source"), hops_distance())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
runtime = AggregateRuntime(
|
|
49
|
+
hops_to_source,
|
|
50
|
+
neighbors=within(1.5), # or cell_neighbors in a grid
|
|
51
|
+
sensors=lambda agent: {"source": agent.unique_id == 1},
|
|
52
|
+
)
|
|
53
|
+
# In Model.step(): one synchronous round on every agent.
|
|
54
|
+
hops = runtime.run_round(model.agents) # {unique_id: result}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
See the [documentation](https://phyelds.github.io/phyelds/integration/mesa/).
|
|
58
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# phyelds-mesa
|
|
2
|
+
|
|
3
|
+
[Mesa](https://mesa.readthedocs.io) integration for [phyelds](https://pypi.org/project/phyelds/):
|
|
4
|
+
run aggregate programs on the agents of a Mesa model.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install phyelds-mesa
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`phyelds-mesa` installs a compatible `phyelds` release and Mesa.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from phyelds.calculus import aggregate
|
|
18
|
+
from phyelds.libraries.device import sense
|
|
19
|
+
from phyelds.libraries.distances import hops_distance
|
|
20
|
+
from phyelds.libraries.spreading import distance_to
|
|
21
|
+
from phyelds.mesa import AggregateRuntime, within
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@aggregate
|
|
25
|
+
def hops_to_source():
|
|
26
|
+
return distance_to(sense("source"), hops_distance())
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
runtime = AggregateRuntime(
|
|
30
|
+
hops_to_source,
|
|
31
|
+
neighbors=within(1.5), # or cell_neighbors in a grid
|
|
32
|
+
sensors=lambda agent: {"source": agent.unique_id == 1},
|
|
33
|
+
)
|
|
34
|
+
# In Model.step(): one synchronous round on every agent.
|
|
35
|
+
hops = runtime.run_round(model.agents) # {unique_id: result}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
See the [documentation](https://phyelds.github.io/phyelds/integration/mesa/).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "phyelds-mesa"
|
|
3
|
+
version = "8.1.0"
|
|
4
|
+
description = "Mesa integration for phyelds: aggregate programs on the agents of a Mesa model"
|
|
5
|
+
license = "Apache-2.0"
|
|
6
|
+
authors = [
|
|
7
|
+
"Davide Domini <davide.domini@unibo.it>",
|
|
8
|
+
"Gianluca Aguzzi <gianluca.aguzzi@unibo.it>"
|
|
9
|
+
]
|
|
10
|
+
homepage = "https://github.com/phyelds/phyelds"
|
|
11
|
+
repository = "https://github.com/phyelds/phyelds"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
|
|
14
|
+
packages = [
|
|
15
|
+
{ include = "phyelds/mesa", from = "src" },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.poetry.dependencies]
|
|
24
|
+
python = ">=3.12,<3.14"
|
|
25
|
+
phyelds = ">=8.0.0,<9.0.0"
|
|
26
|
+
# Mesa imports networkx when it starts, but declares it only in its network extra.
|
|
27
|
+
mesa = { version = ">=3.5.1,<4.0.0", extras = ["network"] }
|
|
28
|
+
|
|
29
|
+
[tool.poetry.group.dev.dependencies]
|
|
30
|
+
pytest = "^8.0.0"
|
|
31
|
+
pylint = "^3.0.0"
|
|
32
|
+
flake8 = "^7.0.0"
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
39
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Run phyelds programs on the agents of a Mesa model.
|
|
3
|
+
|
|
4
|
+
Each agent is a device: its ``unique_id`` is the device id, its neighbors are chosen by a
|
|
5
|
+
function of the agent, and the program runs once per agent in each round.
|
|
6
|
+
"""
|
|
7
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from mesa import Agent
|
|
11
|
+
|
|
12
|
+
from phyelds import engine
|
|
13
|
+
from phyelds.data import State
|
|
14
|
+
from phyelds.vm import NodeContext
|
|
15
|
+
from phyelds.vm.internal import MutableEngine
|
|
16
|
+
|
|
17
|
+
Neighbors = Callable[[Agent], Iterable[Agent]]
|
|
18
|
+
Sensors = Callable[[Agent], Mapping[str, Any]]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AggregateRuntime:
|
|
22
|
+
"""
|
|
23
|
+
Runs an aggregate program on Mesa agents, one synchronous round at a time.
|
|
24
|
+
|
|
25
|
+
In a round every agent receives what its neighbors sent in the previous round, so the
|
|
26
|
+
result does not depend on the order of the agents. Call ``run_round`` from the
|
|
27
|
+
``step`` of the model.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
program: The ``@aggregate`` function to run on each agent.
|
|
31
|
+
neighbors: The agents a given agent talks to, e.g. ``within(radius)`` in a
|
|
32
|
+
continuous space or ``cell_neighbors`` in a grid.
|
|
33
|
+
sensors: Extra sensors of a given agent, added to ``position`` (its position, or
|
|
34
|
+
the coordinate of its cell) and ``time`` (the time of the model).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self, program: Callable[[], Any], *, neighbors: Neighbors, sensors: Sensors | None = None
|
|
39
|
+
):
|
|
40
|
+
self.program = program
|
|
41
|
+
self.neighbors = neighbors
|
|
42
|
+
self.sensors = sensors
|
|
43
|
+
self.outputs: dict[int, dict[str, Any]] = {}
|
|
44
|
+
"""What the program stored with ``store`` on each agent, in the last round."""
|
|
45
|
+
self._states: dict[int, dict[str, Any]] = {}
|
|
46
|
+
self._outboxes: dict[int, dict[str, Any]] = {}
|
|
47
|
+
|
|
48
|
+
def run_round(self, agents: Iterable[Agent]) -> dict[int, Any]:
|
|
49
|
+
"""
|
|
50
|
+
Run the program once on each of ``agents``.
|
|
51
|
+
|
|
52
|
+
Agents missing from a round are forgotten: their state and messages are dropped.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
agents: The agents of this round, e.g. ``model.agents``.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
The result of the program on each agent, by ``unique_id``.
|
|
59
|
+
"""
|
|
60
|
+
states, outboxes, outputs, results = {}, {}, {}, {}
|
|
61
|
+
for agent in agents:
|
|
62
|
+
inbox = {
|
|
63
|
+
neighbor.unique_id: self._outboxes[neighbor.unique_id]
|
|
64
|
+
for neighbor in self.neighbors(agent)
|
|
65
|
+
if neighbor.unique_id in self._outboxes
|
|
66
|
+
}
|
|
67
|
+
node_engine = MutableEngine()
|
|
68
|
+
engine.set(node_engine)
|
|
69
|
+
context = NodeContext(agent.unique_id, self._sense(agent))
|
|
70
|
+
node_engine.setup(context, inbox, self._states.get(agent.unique_id, {}))
|
|
71
|
+
result = self.program()
|
|
72
|
+
outboxes[agent.unique_id] = node_engine.cooldown()
|
|
73
|
+
states[agent.unique_id] = node_engine.state_trace()
|
|
74
|
+
outputs[agent.unique_id] = context.outputs
|
|
75
|
+
results[agent.unique_id] = result.value if isinstance(result, State) else result
|
|
76
|
+
self._states, self._outboxes, self.outputs = states, outboxes, outputs
|
|
77
|
+
return results
|
|
78
|
+
|
|
79
|
+
def reset(self) -> None:
|
|
80
|
+
"""Forget the state and the messages of every agent."""
|
|
81
|
+
self._states, self._outboxes, self.outputs = {}, {}, {}
|
|
82
|
+
|
|
83
|
+
def _sense(self, agent: Agent) -> dict[str, Any]:
|
|
84
|
+
"""The default sensors of ``agent``, updated with the extra ones."""
|
|
85
|
+
sensors = {"position": _position(agent), "time": agent.model.time}
|
|
86
|
+
if self.sensors is not None:
|
|
87
|
+
sensors.update(self.sensors(agent))
|
|
88
|
+
return sensors
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def within(radius: float) -> Neighbors:
|
|
92
|
+
"""
|
|
93
|
+
Neighbors in a continuous space: the agents closer than ``radius``.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
radius: The communication range.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
A function giving the neighbors of a ``ContinuousSpaceAgent``.
|
|
100
|
+
"""
|
|
101
|
+
return lambda agent: agent.get_neighbors_in_radius(radius)[0]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def cell_neighbors(agent: Agent) -> list[Agent]:
|
|
105
|
+
"""
|
|
106
|
+
Neighbors in a grid or network: the other agents in the cell of ``agent`` and around it.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
agent: A ``CellAgent``.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
The agents of its cell, but itself, and of the neighborhood of its cell.
|
|
113
|
+
"""
|
|
114
|
+
same_cell = [other for other in agent.cell.agents if other is not agent]
|
|
115
|
+
return same_cell + list(agent.cell.neighborhood.agents)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _position(agent: Agent) -> tuple | None:
|
|
119
|
+
"""Where ``agent`` is: its position in a continuous space, or the coordinate of its cell."""
|
|
120
|
+
position = getattr(agent, "position", None)
|
|
121
|
+
if position is not None:
|
|
122
|
+
return tuple(float(coordinate) for coordinate in position)
|
|
123
|
+
cell = getattr(agent, "cell", None)
|
|
124
|
+
return None if cell is None else cell.coordinate
|