mpe2 0.0.1__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.
- mpe2-0.0.1/LICENSE +21 -0
- mpe2-0.0.1/MANIFEST.in +3 -0
- mpe2-0.0.1/PKG-INFO +44 -0
- mpe2-0.0.1/README.md +18 -0
- mpe2-0.0.1/mpe2/__init__.py +1 -0
- mpe2-0.0.1/mpe2/_mpe_utils/__init__.py +0 -0
- mpe2-0.0.1/mpe2/_mpe_utils/core.py +218 -0
- mpe2-0.0.1/mpe2/_mpe_utils/scenario.py +6 -0
- mpe2-0.0.1/mpe2/_mpe_utils/secrcode.ttf +0 -0
- mpe2-0.0.1/mpe2/_mpe_utils/simple_env.py +360 -0
- mpe2-0.0.1/mpe2/all_modules.py +23 -0
- mpe2-0.0.1/mpe2/simple/simple.py +131 -0
- mpe2-0.0.1/mpe2/simple_adversary/simple_adversary.py +260 -0
- mpe2-0.0.1/mpe2/simple_adversary_v3.py +3 -0
- mpe2-0.0.1/mpe2/simple_crypto/simple_crypto.py +263 -0
- mpe2-0.0.1/mpe2/simple_crypto_v3.py +3 -0
- mpe2-0.0.1/mpe2/simple_push/simple_push.py +199 -0
- mpe2-0.0.1/mpe2/simple_push_v3.py +3 -0
- mpe2-0.0.1/mpe2/simple_reference/simple_reference.py +186 -0
- mpe2-0.0.1/mpe2/simple_reference_v3.py +3 -0
- mpe2-0.0.1/mpe2/simple_speaker_listener/simple_speaker_listener.py +181 -0
- mpe2-0.0.1/mpe2/simple_speaker_listener_v4.py +7 -0
- mpe2-0.0.1/mpe2/simple_spread/simple_spread.py +204 -0
- mpe2-0.0.1/mpe2/simple_spread_v3.py +3 -0
- mpe2-0.0.1/mpe2/simple_tag/simple_tag.py +275 -0
- mpe2-0.0.1/mpe2/simple_tag_v3.py +3 -0
- mpe2-0.0.1/mpe2/simple_v3.py +3 -0
- mpe2-0.0.1/mpe2/simple_world_comm/simple_world_comm.py +480 -0
- mpe2-0.0.1/mpe2/simple_world_comm_v3.py +3 -0
- mpe2-0.0.1/mpe2.egg-info/PKG-INFO +44 -0
- mpe2-0.0.1/mpe2.egg-info/SOURCES.txt +35 -0
- mpe2-0.0.1/mpe2.egg-info/dependency_links.txt +1 -0
- mpe2-0.0.1/mpe2.egg-info/requires.txt +10 -0
- mpe2-0.0.1/mpe2.egg-info/top_level.txt +1 -0
- mpe2-0.0.1/pyproject.toml +44 -0
- mpe2-0.0.1/setup.cfg +4 -0
- mpe2-0.0.1/setup.py +21 -0
mpe2-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Farama Foundation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mpe2-0.0.1/MANIFEST.in
ADDED
mpe2-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mpe2
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Multi Particle Environments Version 2
|
|
5
|
+
Author-email: Farama Foundation <contact@farama.org>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Keywords: Reinforcement Learning,game,RL,AI,gymnasium
|
|
8
|
+
Classifier: Development Status :: 1 - Planning
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: pettingzoo>=1.24.3
|
|
18
|
+
Requires-Dist: pygame>=2.6.0
|
|
19
|
+
Provides-Extra: testing
|
|
20
|
+
Requires-Dist: pynput==1.7.6; extra == "testing"
|
|
21
|
+
Requires-Dist: pytest==8.0.0; extra == "testing"
|
|
22
|
+
Requires-Dist: pytest-cov==4.1.0; extra == "testing"
|
|
23
|
+
Requires-Dist: pytest-xdist==3.5.0; extra == "testing"
|
|
24
|
+
Requires-Dist: pre-commit==3.5.0; extra == "testing"
|
|
25
|
+
Requires-Dist: pytest-markdown-docs==0.5.0; extra == "testing"
|
|
26
|
+
|
|
27
|
+
# MPE2
|
|
28
|
+
|
|
29
|
+
## Installation Steps
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# create venv
|
|
33
|
+
conda create -n mpe2_env python=3.10
|
|
34
|
+
conda activate mpe2_env
|
|
35
|
+
|
|
36
|
+
# clone repo
|
|
37
|
+
git clone https://github.com/Farama-Foundation/MPE2.git
|
|
38
|
+
|
|
39
|
+
# for development
|
|
40
|
+
python3 -m pip install -e .[testing, documentation]
|
|
41
|
+
|
|
42
|
+
# for usage
|
|
43
|
+
python3 -m pip install -e .
|
|
44
|
+
```
|
mpe2-0.0.1/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# MPE2
|
|
2
|
+
|
|
3
|
+
## Installation Steps
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# create venv
|
|
7
|
+
conda create -n mpe2_env python=3.10
|
|
8
|
+
conda activate mpe2_env
|
|
9
|
+
|
|
10
|
+
# clone repo
|
|
11
|
+
git clone https://github.com/Farama-Foundation/MPE2.git
|
|
12
|
+
|
|
13
|
+
# for development
|
|
14
|
+
python3 -m pip install -e .[testing, documentation]
|
|
15
|
+
|
|
16
|
+
# for usage
|
|
17
|
+
python3 -m pip install -e .
|
|
18
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
|
File without changes
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EntityState: # physical/external base state of all entities
|
|
5
|
+
def __init__(self):
|
|
6
|
+
# physical position
|
|
7
|
+
self.p_pos = None
|
|
8
|
+
# physical velocity
|
|
9
|
+
self.p_vel = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentState(
|
|
13
|
+
EntityState
|
|
14
|
+
): # state of agents (including communication and internal/mental state)
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__()
|
|
17
|
+
# communication utterance
|
|
18
|
+
self.c = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Action: # action of the agent
|
|
22
|
+
def __init__(self):
|
|
23
|
+
# physical action
|
|
24
|
+
self.u = None
|
|
25
|
+
# communication action
|
|
26
|
+
self.c = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Entity: # properties and state of physical world entity
|
|
30
|
+
def __init__(self):
|
|
31
|
+
# name
|
|
32
|
+
self.name = ""
|
|
33
|
+
# properties:
|
|
34
|
+
self.size = 0.050
|
|
35
|
+
# entity can move / be pushed
|
|
36
|
+
self.movable = False
|
|
37
|
+
# entity collides with others
|
|
38
|
+
self.collide = True
|
|
39
|
+
# material density (affects mass)
|
|
40
|
+
self.density = 25.0
|
|
41
|
+
# color
|
|
42
|
+
self.color = None
|
|
43
|
+
# max speed and accel
|
|
44
|
+
self.max_speed = None
|
|
45
|
+
self.accel = None
|
|
46
|
+
# state
|
|
47
|
+
self.state = EntityState()
|
|
48
|
+
# mass
|
|
49
|
+
self.initial_mass = 1.0
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def mass(self):
|
|
53
|
+
return self.initial_mass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Landmark(Entity): # properties of landmark entities
|
|
57
|
+
def __init__(self):
|
|
58
|
+
super().__init__()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Agent(Entity): # properties of agent entities
|
|
62
|
+
def __init__(self):
|
|
63
|
+
super().__init__()
|
|
64
|
+
# agents are movable by default
|
|
65
|
+
self.movable = True
|
|
66
|
+
# cannot send communication signals
|
|
67
|
+
self.silent = False
|
|
68
|
+
# cannot observe the world
|
|
69
|
+
self.blind = False
|
|
70
|
+
# physical motor noise amount
|
|
71
|
+
self.u_noise = None
|
|
72
|
+
# communication noise amount
|
|
73
|
+
self.c_noise = None
|
|
74
|
+
# control range
|
|
75
|
+
self.u_range = 1.0
|
|
76
|
+
# state
|
|
77
|
+
self.state = AgentState()
|
|
78
|
+
# action
|
|
79
|
+
self.action = Action()
|
|
80
|
+
# script behavior to execute
|
|
81
|
+
self.action_callback = None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class World: # multi-agent world
|
|
85
|
+
def __init__(self):
|
|
86
|
+
# list of agents and entities (can change at execution-time!)
|
|
87
|
+
self.agents = []
|
|
88
|
+
self.landmarks = []
|
|
89
|
+
# communication channel dimensionality
|
|
90
|
+
self.dim_c = 0
|
|
91
|
+
# position dimensionality
|
|
92
|
+
self.dim_p = 2
|
|
93
|
+
# color dimensionality
|
|
94
|
+
self.dim_color = 3
|
|
95
|
+
# simulation timestep
|
|
96
|
+
self.dt = 0.1
|
|
97
|
+
# physical damping
|
|
98
|
+
self.damping = 0.25
|
|
99
|
+
# contact response parameters
|
|
100
|
+
self.contact_force = 1e2
|
|
101
|
+
self.contact_margin = 1e-3
|
|
102
|
+
|
|
103
|
+
# return all entities in the world
|
|
104
|
+
@property
|
|
105
|
+
def entities(self):
|
|
106
|
+
return self.agents + self.landmarks
|
|
107
|
+
|
|
108
|
+
# return all agents controllable by external policies
|
|
109
|
+
@property
|
|
110
|
+
def policy_agents(self):
|
|
111
|
+
return [agent for agent in self.agents if agent.action_callback is None]
|
|
112
|
+
|
|
113
|
+
# return all agents controlled by world scripts
|
|
114
|
+
@property
|
|
115
|
+
def scripted_agents(self):
|
|
116
|
+
return [agent for agent in self.agents if agent.action_callback is not None]
|
|
117
|
+
|
|
118
|
+
# update state of the world
|
|
119
|
+
def step(self):
|
|
120
|
+
# set actions for scripted agents
|
|
121
|
+
for agent in self.scripted_agents:
|
|
122
|
+
agent.action = agent.action_callback(agent, self)
|
|
123
|
+
# gather forces applied to entities
|
|
124
|
+
p_force = [None] * len(self.entities)
|
|
125
|
+
# apply agent physical controls
|
|
126
|
+
p_force = self.apply_action_force(p_force)
|
|
127
|
+
# apply environment forces
|
|
128
|
+
p_force = self.apply_environment_force(p_force)
|
|
129
|
+
# integrate physical state
|
|
130
|
+
self.integrate_state(p_force)
|
|
131
|
+
# update agent state
|
|
132
|
+
for agent in self.agents:
|
|
133
|
+
self.update_agent_state(agent)
|
|
134
|
+
|
|
135
|
+
# gather agent action forces
|
|
136
|
+
def apply_action_force(self, p_force):
|
|
137
|
+
# set applied forces
|
|
138
|
+
for i, agent in enumerate(self.agents):
|
|
139
|
+
if agent.movable:
|
|
140
|
+
noise = (
|
|
141
|
+
np.random.randn(*agent.action.u.shape) * agent.u_noise
|
|
142
|
+
if agent.u_noise
|
|
143
|
+
else 0.0
|
|
144
|
+
)
|
|
145
|
+
p_force[i] = agent.action.u + noise
|
|
146
|
+
return p_force
|
|
147
|
+
|
|
148
|
+
# gather physical forces acting on entities
|
|
149
|
+
def apply_environment_force(self, p_force):
|
|
150
|
+
# simple (but inefficient) collision response
|
|
151
|
+
for a, entity_a in enumerate(self.entities):
|
|
152
|
+
for b, entity_b in enumerate(self.entities):
|
|
153
|
+
if b <= a:
|
|
154
|
+
continue
|
|
155
|
+
[f_a, f_b] = self.get_collision_force(entity_a, entity_b)
|
|
156
|
+
if f_a is not None:
|
|
157
|
+
if p_force[a] is None:
|
|
158
|
+
p_force[a] = 0.0
|
|
159
|
+
p_force[a] = f_a + p_force[a]
|
|
160
|
+
if f_b is not None:
|
|
161
|
+
if p_force[b] is None:
|
|
162
|
+
p_force[b] = 0.0
|
|
163
|
+
p_force[b] = f_b + p_force[b]
|
|
164
|
+
return p_force
|
|
165
|
+
|
|
166
|
+
# integrate physical state
|
|
167
|
+
def integrate_state(self, p_force):
|
|
168
|
+
for i, entity in enumerate(self.entities):
|
|
169
|
+
if not entity.movable:
|
|
170
|
+
continue
|
|
171
|
+
entity.state.p_pos += entity.state.p_vel * self.dt
|
|
172
|
+
entity.state.p_vel = entity.state.p_vel * (1 - self.damping)
|
|
173
|
+
if p_force[i] is not None:
|
|
174
|
+
entity.state.p_vel += (p_force[i] / entity.mass) * self.dt
|
|
175
|
+
if entity.max_speed is not None:
|
|
176
|
+
speed = np.sqrt(
|
|
177
|
+
np.square(entity.state.p_vel[0]) + np.square(entity.state.p_vel[1])
|
|
178
|
+
)
|
|
179
|
+
if speed > entity.max_speed:
|
|
180
|
+
entity.state.p_vel = (
|
|
181
|
+
entity.state.p_vel
|
|
182
|
+
/ np.sqrt(
|
|
183
|
+
np.square(entity.state.p_vel[0])
|
|
184
|
+
+ np.square(entity.state.p_vel[1])
|
|
185
|
+
)
|
|
186
|
+
* entity.max_speed
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def update_agent_state(self, agent):
|
|
190
|
+
# set communication state (directly for now)
|
|
191
|
+
if agent.silent:
|
|
192
|
+
agent.state.c = np.zeros(self.dim_c)
|
|
193
|
+
else:
|
|
194
|
+
noise = (
|
|
195
|
+
np.random.randn(*agent.action.c.shape) * agent.c_noise
|
|
196
|
+
if agent.c_noise
|
|
197
|
+
else 0.0
|
|
198
|
+
)
|
|
199
|
+
agent.state.c = agent.action.c + noise
|
|
200
|
+
|
|
201
|
+
# get collision forces for any contact between two entities
|
|
202
|
+
def get_collision_force(self, entity_a, entity_b):
|
|
203
|
+
if (not entity_a.collide) or (not entity_b.collide):
|
|
204
|
+
return [None, None] # not a collider
|
|
205
|
+
if entity_a is entity_b:
|
|
206
|
+
return [None, None] # don't collide against itself
|
|
207
|
+
# compute actual distance between entities
|
|
208
|
+
delta_pos = entity_a.state.p_pos - entity_b.state.p_pos
|
|
209
|
+
dist = np.sqrt(np.sum(np.square(delta_pos)))
|
|
210
|
+
# minimum allowable distance
|
|
211
|
+
dist_min = entity_a.size + entity_b.size
|
|
212
|
+
# softmax penetration
|
|
213
|
+
k = self.contact_margin
|
|
214
|
+
penetration = np.logaddexp(0, -(dist - dist_min) / k) * k
|
|
215
|
+
force = self.contact_force * delta_pos / dist * penetration
|
|
216
|
+
force_a = +force if entity_a.movable else None
|
|
217
|
+
force_b = -force if entity_b.movable else None
|
|
218
|
+
return [force_a, force_b]
|
|
Binary file
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import gymnasium
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pygame
|
|
6
|
+
from gymnasium import spaces
|
|
7
|
+
from gymnasium.utils import seeding
|
|
8
|
+
from pettingzoo import AECEnv
|
|
9
|
+
from pettingzoo.utils import wrappers
|
|
10
|
+
from pettingzoo.utils.agent_selector import AgentSelector
|
|
11
|
+
|
|
12
|
+
from mpe2._mpe_utils.core import Agent
|
|
13
|
+
|
|
14
|
+
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def make_env(raw_env):
|
|
18
|
+
def env(**kwargs):
|
|
19
|
+
env = raw_env(**kwargs)
|
|
20
|
+
if env.continuous_actions:
|
|
21
|
+
env = wrappers.ClipOutOfBoundsWrapper(env)
|
|
22
|
+
else:
|
|
23
|
+
env = wrappers.AssertOutOfBoundsWrapper(env)
|
|
24
|
+
env = wrappers.OrderEnforcingWrapper(env)
|
|
25
|
+
return env
|
|
26
|
+
|
|
27
|
+
return env
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SimpleEnv(AECEnv):
|
|
31
|
+
metadata = {
|
|
32
|
+
"render_modes": ["human", "rgb_array"],
|
|
33
|
+
"is_parallelizable": True,
|
|
34
|
+
"render_fps": 10,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
scenario,
|
|
40
|
+
world,
|
|
41
|
+
max_cycles,
|
|
42
|
+
render_mode=None,
|
|
43
|
+
continuous_actions=False,
|
|
44
|
+
local_ratio=None,
|
|
45
|
+
dynamic_rescaling=False,
|
|
46
|
+
):
|
|
47
|
+
super().__init__()
|
|
48
|
+
|
|
49
|
+
self.render_mode = render_mode
|
|
50
|
+
pygame.init()
|
|
51
|
+
self.viewer = None
|
|
52
|
+
self.width = 700
|
|
53
|
+
self.height = 700
|
|
54
|
+
self.screen = pygame.Surface([self.width, self.height])
|
|
55
|
+
self.max_size = 1
|
|
56
|
+
self.game_font = pygame.freetype.Font(
|
|
57
|
+
os.path.join(os.path.dirname(__file__), "secrcode.ttf"), 24
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Set up the drawing window
|
|
61
|
+
|
|
62
|
+
self.renderOn = False
|
|
63
|
+
self._seed()
|
|
64
|
+
|
|
65
|
+
self.max_cycles = max_cycles
|
|
66
|
+
self.scenario = scenario
|
|
67
|
+
self.world = world
|
|
68
|
+
self.continuous_actions = continuous_actions
|
|
69
|
+
self.local_ratio = local_ratio
|
|
70
|
+
self.dynamic_rescaling = dynamic_rescaling
|
|
71
|
+
|
|
72
|
+
self.scenario.reset_world(self.world, self.np_random)
|
|
73
|
+
|
|
74
|
+
self.agents = [agent.name for agent in self.world.agents]
|
|
75
|
+
self.possible_agents = self.agents[:]
|
|
76
|
+
self._index_map = {
|
|
77
|
+
agent.name: idx for idx, agent in enumerate(self.world.agents)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
self._agent_selector = AgentSelector(self.agents)
|
|
81
|
+
|
|
82
|
+
# set spaces
|
|
83
|
+
self.action_spaces = dict()
|
|
84
|
+
self.observation_spaces = dict()
|
|
85
|
+
state_dim = 0
|
|
86
|
+
for agent in self.world.agents:
|
|
87
|
+
if agent.movable:
|
|
88
|
+
space_dim = self.world.dim_p * 2 + 1
|
|
89
|
+
elif self.continuous_actions:
|
|
90
|
+
space_dim = 0
|
|
91
|
+
else:
|
|
92
|
+
space_dim = 1
|
|
93
|
+
if not agent.silent:
|
|
94
|
+
if self.continuous_actions:
|
|
95
|
+
space_dim += self.world.dim_c
|
|
96
|
+
else:
|
|
97
|
+
space_dim *= self.world.dim_c
|
|
98
|
+
|
|
99
|
+
obs_dim = len(self.scenario.observation(agent, self.world))
|
|
100
|
+
state_dim += obs_dim
|
|
101
|
+
if self.continuous_actions:
|
|
102
|
+
self.action_spaces[agent.name] = spaces.Box(
|
|
103
|
+
low=0, high=1, shape=(space_dim,)
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
self.action_spaces[agent.name] = spaces.Discrete(space_dim)
|
|
107
|
+
self.observation_spaces[agent.name] = spaces.Box(
|
|
108
|
+
low=-np.float32(np.inf),
|
|
109
|
+
high=+np.float32(np.inf),
|
|
110
|
+
shape=(obs_dim,),
|
|
111
|
+
dtype=np.float32,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
self.state_space = spaces.Box(
|
|
115
|
+
low=-np.float32(np.inf),
|
|
116
|
+
high=+np.float32(np.inf),
|
|
117
|
+
shape=(state_dim,),
|
|
118
|
+
dtype=np.float32,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Get the original cam_range
|
|
122
|
+
# This will be used to scale the rendering
|
|
123
|
+
all_poses = [entity.state.p_pos for entity in self.world.entities]
|
|
124
|
+
self.original_cam_range = np.max(np.abs(np.array(all_poses)))
|
|
125
|
+
|
|
126
|
+
self.steps = 0
|
|
127
|
+
|
|
128
|
+
self.current_actions = [None] * self.num_agents
|
|
129
|
+
|
|
130
|
+
def observation_space(self, agent):
|
|
131
|
+
return self.observation_spaces[agent]
|
|
132
|
+
|
|
133
|
+
def action_space(self, agent):
|
|
134
|
+
return self.action_spaces[agent]
|
|
135
|
+
|
|
136
|
+
def _seed(self, seed=None):
|
|
137
|
+
self.np_random, seed = seeding.np_random(seed)
|
|
138
|
+
|
|
139
|
+
def observe(self, agent):
|
|
140
|
+
return self.scenario.observation(
|
|
141
|
+
self.world.agents[self._index_map[agent]], self.world
|
|
142
|
+
).astype(np.float32)
|
|
143
|
+
|
|
144
|
+
def state(self):
|
|
145
|
+
states = tuple(
|
|
146
|
+
self.scenario.observation(
|
|
147
|
+
self.world.agents[self._index_map[agent]], self.world
|
|
148
|
+
).astype(np.float32)
|
|
149
|
+
for agent in self.possible_agents
|
|
150
|
+
)
|
|
151
|
+
return np.concatenate(states, axis=None)
|
|
152
|
+
|
|
153
|
+
def reset(self, seed=None, options=None):
|
|
154
|
+
if seed is not None:
|
|
155
|
+
self._seed(seed=seed)
|
|
156
|
+
self.scenario.reset_world(self.world, self.np_random)
|
|
157
|
+
|
|
158
|
+
self.agents = self.possible_agents[:]
|
|
159
|
+
self.rewards = {name: 0.0 for name in self.agents}
|
|
160
|
+
self._cumulative_rewards = {name: 0.0 for name in self.agents}
|
|
161
|
+
self.terminations = {name: False for name in self.agents}
|
|
162
|
+
self.truncations = {name: False for name in self.agents}
|
|
163
|
+
self.infos = {name: {} for name in self.agents}
|
|
164
|
+
|
|
165
|
+
self.agent_selection = self._agent_selector.reset()
|
|
166
|
+
self.steps = 0
|
|
167
|
+
|
|
168
|
+
self.current_actions = [None] * self.num_agents
|
|
169
|
+
|
|
170
|
+
def _execute_world_step(self):
|
|
171
|
+
# set action for each agent
|
|
172
|
+
for i, agent in enumerate(self.world.agents):
|
|
173
|
+
action = self.current_actions[i]
|
|
174
|
+
scenario_action = []
|
|
175
|
+
if agent.movable:
|
|
176
|
+
mdim = self.world.dim_p * 2 + 1
|
|
177
|
+
if self.continuous_actions:
|
|
178
|
+
scenario_action.append(action[0:mdim])
|
|
179
|
+
action = action[mdim:]
|
|
180
|
+
else:
|
|
181
|
+
scenario_action.append(action % mdim)
|
|
182
|
+
action //= mdim
|
|
183
|
+
if not agent.silent:
|
|
184
|
+
scenario_action.append(action)
|
|
185
|
+
self._set_action(scenario_action, agent, self.action_spaces[agent.name])
|
|
186
|
+
|
|
187
|
+
self.world.step()
|
|
188
|
+
|
|
189
|
+
global_reward = 0.0
|
|
190
|
+
if self.local_ratio is not None:
|
|
191
|
+
global_reward = float(self.scenario.global_reward(self.world))
|
|
192
|
+
|
|
193
|
+
for agent in self.world.agents:
|
|
194
|
+
agent_reward = float(self.scenario.reward(agent, self.world))
|
|
195
|
+
if self.local_ratio is not None:
|
|
196
|
+
reward = (
|
|
197
|
+
global_reward * (1 - self.local_ratio)
|
|
198
|
+
+ agent_reward * self.local_ratio
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
reward = agent_reward
|
|
202
|
+
|
|
203
|
+
self.rewards[agent.name] = reward
|
|
204
|
+
|
|
205
|
+
# set env action for a particular agent
|
|
206
|
+
def _set_action(self, action, agent, action_space, time=None):
|
|
207
|
+
agent.action.u = np.zeros(self.world.dim_p)
|
|
208
|
+
agent.action.c = np.zeros(self.world.dim_c)
|
|
209
|
+
|
|
210
|
+
if agent.movable:
|
|
211
|
+
# physical action
|
|
212
|
+
agent.action.u = np.zeros(self.world.dim_p)
|
|
213
|
+
if self.continuous_actions:
|
|
214
|
+
# Process continuous action as in OpenAI MPE
|
|
215
|
+
# Note: this ordering preserves the same movement direction as in the discrete case
|
|
216
|
+
agent.action.u[0] += action[0][2] - action[0][1]
|
|
217
|
+
agent.action.u[1] += action[0][4] - action[0][3]
|
|
218
|
+
else:
|
|
219
|
+
# process discrete action
|
|
220
|
+
if action[0] == 1:
|
|
221
|
+
agent.action.u[0] = -1.0
|
|
222
|
+
if action[0] == 2:
|
|
223
|
+
agent.action.u[0] = +1.0
|
|
224
|
+
if action[0] == 3:
|
|
225
|
+
agent.action.u[1] = -1.0
|
|
226
|
+
if action[0] == 4:
|
|
227
|
+
agent.action.u[1] = +1.0
|
|
228
|
+
sensitivity = 5.0
|
|
229
|
+
if agent.accel is not None:
|
|
230
|
+
sensitivity = agent.accel
|
|
231
|
+
agent.action.u *= sensitivity
|
|
232
|
+
action = action[1:]
|
|
233
|
+
if not agent.silent:
|
|
234
|
+
# communication action
|
|
235
|
+
if self.continuous_actions:
|
|
236
|
+
agent.action.c = action[0]
|
|
237
|
+
else:
|
|
238
|
+
agent.action.c = np.zeros(self.world.dim_c)
|
|
239
|
+
agent.action.c[action[0]] = 1.0
|
|
240
|
+
action = action[1:]
|
|
241
|
+
# make sure we used all elements of action
|
|
242
|
+
assert len(action) == 0
|
|
243
|
+
|
|
244
|
+
def step(self, action):
|
|
245
|
+
if (
|
|
246
|
+
self.terminations[self.agent_selection]
|
|
247
|
+
or self.truncations[self.agent_selection]
|
|
248
|
+
):
|
|
249
|
+
self._was_dead_step(action)
|
|
250
|
+
return
|
|
251
|
+
cur_agent = self.agent_selection
|
|
252
|
+
current_idx = self._index_map[self.agent_selection]
|
|
253
|
+
next_idx = (current_idx + 1) % self.num_agents
|
|
254
|
+
self.agent_selection = self._agent_selector.next()
|
|
255
|
+
|
|
256
|
+
self.current_actions[current_idx] = action
|
|
257
|
+
|
|
258
|
+
if next_idx == 0:
|
|
259
|
+
self._execute_world_step()
|
|
260
|
+
self.steps += 1
|
|
261
|
+
if self.steps >= self.max_cycles:
|
|
262
|
+
for a in self.agents:
|
|
263
|
+
self.truncations[a] = True
|
|
264
|
+
else:
|
|
265
|
+
self._clear_rewards()
|
|
266
|
+
|
|
267
|
+
self._cumulative_rewards[cur_agent] = 0
|
|
268
|
+
self._accumulate_rewards()
|
|
269
|
+
|
|
270
|
+
if self.render_mode == "human":
|
|
271
|
+
self.render()
|
|
272
|
+
|
|
273
|
+
def enable_render(self, mode="human"):
|
|
274
|
+
if not self.renderOn and mode == "human":
|
|
275
|
+
self.screen = pygame.display.set_mode(self.screen.get_size())
|
|
276
|
+
self.clock = pygame.time.Clock()
|
|
277
|
+
self.renderOn = True
|
|
278
|
+
|
|
279
|
+
def render(self):
|
|
280
|
+
if self.render_mode is None:
|
|
281
|
+
gymnasium.logger.warn(
|
|
282
|
+
"You are calling render method without specifying any render mode."
|
|
283
|
+
)
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
self.enable_render(self.render_mode)
|
|
287
|
+
|
|
288
|
+
self.draw()
|
|
289
|
+
if self.render_mode == "rgb_array":
|
|
290
|
+
observation = np.array(pygame.surfarray.pixels3d(self.screen))
|
|
291
|
+
return np.transpose(observation, axes=(1, 0, 2))
|
|
292
|
+
elif self.render_mode == "human":
|
|
293
|
+
pygame.display.flip()
|
|
294
|
+
self.clock.tick(self.metadata["render_fps"])
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
def draw(self):
|
|
298
|
+
# clear screen
|
|
299
|
+
self.screen.fill((255, 255, 255))
|
|
300
|
+
|
|
301
|
+
# update bounds to center around agent
|
|
302
|
+
all_poses = [entity.state.p_pos for entity in self.world.entities]
|
|
303
|
+
cam_range = np.max(np.abs(np.array(all_poses)))
|
|
304
|
+
|
|
305
|
+
# The scaling factor is used for dynamic rescaling of the rendering - a.k.a Zoom In/Zoom Out effect
|
|
306
|
+
# The 0.9 is a factor to keep the entities from appearing "too" out-of-bounds
|
|
307
|
+
scaling_factor = 0.9 * self.original_cam_range / cam_range
|
|
308
|
+
|
|
309
|
+
# update geometry and text positions
|
|
310
|
+
text_line = 0
|
|
311
|
+
for e, entity in enumerate(self.world.entities):
|
|
312
|
+
# geometry
|
|
313
|
+
x, y = entity.state.p_pos
|
|
314
|
+
y *= (
|
|
315
|
+
-1
|
|
316
|
+
) # this makes the display mimic the old pyglet setup (ie. flips image)
|
|
317
|
+
x = (
|
|
318
|
+
(x / cam_range) * self.width // 2 * 0.9
|
|
319
|
+
) # the .9 is just to keep entities from appearing "too" out-of-bounds
|
|
320
|
+
y = (y / cam_range) * self.height // 2 * 0.9
|
|
321
|
+
x += self.width // 2
|
|
322
|
+
y += self.height // 2
|
|
323
|
+
|
|
324
|
+
# 350 is an arbitrary scale factor to get pygame to render similar sizes as pyglet
|
|
325
|
+
if self.dynamic_rescaling:
|
|
326
|
+
radius = entity.size * 350 * scaling_factor
|
|
327
|
+
else:
|
|
328
|
+
radius = entity.size * 350
|
|
329
|
+
|
|
330
|
+
pygame.draw.circle(self.screen, entity.color * 200, (x, y), radius)
|
|
331
|
+
pygame.draw.circle(self.screen, (0, 0, 0), (x, y), radius, 1) # borders
|
|
332
|
+
assert (
|
|
333
|
+
0 < x < self.width and 0 < y < self.height
|
|
334
|
+
), f"Coordinates {(x, y)} are out of bounds."
|
|
335
|
+
|
|
336
|
+
# text
|
|
337
|
+
if isinstance(entity, Agent):
|
|
338
|
+
if entity.silent:
|
|
339
|
+
continue
|
|
340
|
+
if np.all(entity.state.c == 0):
|
|
341
|
+
word = "_"
|
|
342
|
+
elif self.continuous_actions:
|
|
343
|
+
word = (
|
|
344
|
+
"[" + ",".join([f"{comm:.2f}" for comm in entity.state.c]) + "]"
|
|
345
|
+
)
|
|
346
|
+
else:
|
|
347
|
+
word = alphabet[np.argmax(entity.state.c)]
|
|
348
|
+
|
|
349
|
+
message = entity.name + " sends " + word + " "
|
|
350
|
+
message_x_pos = self.width * 0.05
|
|
351
|
+
message_y_pos = self.height * 0.95 - (self.height * 0.05 * text_line)
|
|
352
|
+
self.game_font.render_to(
|
|
353
|
+
self.screen, (message_x_pos, message_y_pos), message, (0, 0, 0)
|
|
354
|
+
)
|
|
355
|
+
text_line += 1
|
|
356
|
+
|
|
357
|
+
def close(self):
|
|
358
|
+
if self.screen is not None:
|
|
359
|
+
pygame.quit()
|
|
360
|
+
self.screen = None
|