mirror of
https://github.com/illiumst/marl-factory-grid.git
synced 2025-07-04 08:31:35 +02:00
Doors now Working,
Pypi install adjustments.
This commit is contained in:
1
MANIFEST.in
Normal file
1
MANIFEST.in
Normal file
@ -0,0 +1 @@
|
||||
global-include *.txt *.png *.jpg
|
@ -3,14 +3,11 @@
|
||||
Tackling emergent dysfunctions (EDYs) in cooperation with Fraunhofer-IKS
|
||||
|
||||
## Setup
|
||||
1. Make sure to install `virtualenv` using `pip install virtualenv`
|
||||
2. Create a new virtual environment `virtualenv venv`
|
||||
3. Activate the virtual environment `source venv/bin/activate`
|
||||
4. Install the required dependencies `pip install -r requirements.txt`
|
||||
##
|
||||
Just install this environment by `pip install marl-factory-grid`.
|
||||
|
||||
## First Steps
|
||||
|
||||
|
||||
### Quickstart
|
||||
Most of the env. objects (entites, rules and assets) can be loaded automatically.
|
||||
Just define what your environment needs in a *yaml*-configfile like:
|
||||
|
@ -1,10 +1,11 @@
|
||||
import itertools
|
||||
from random import choice
|
||||
|
||||
import numpy as np
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation import traveling_salesman as tsp
|
||||
|
||||
from environment.utils.helpers import points_to_graph
|
||||
|
||||
from modules.doors import constants as do
|
||||
from environment import constants as c
|
||||
@ -15,6 +16,41 @@ from abc import abstractmethod, ABC
|
||||
future_planning = 7
|
||||
|
||||
|
||||
def points_to_graph(coordiniates_or_tiles, allow_euclidean_connections=True, allow_manhattan_connections=True):
|
||||
"""
|
||||
Given a set of coordinates, this function contructs a non-directed graph, by conncting adjected points.
|
||||
There are three combinations of settings:
|
||||
Allow all neigbors: Distance(a, b) <= sqrt(2)
|
||||
Allow only manhattan: Distance(a, b) == 1
|
||||
Allow only euclidean: Distance(a, b) == sqrt(2)
|
||||
|
||||
|
||||
:param coordiniates_or_tiles: A set of coordinates.
|
||||
:type coordiniates_or_tiles: Tiles
|
||||
:param allow_euclidean_connections: Whether to regard diagonal adjected cells as neighbors
|
||||
:type: bool
|
||||
:param allow_manhattan_connections: Whether to regard directly adjected cells as neighbors
|
||||
:type: bool
|
||||
|
||||
:return: A graph with nodes that are conneceted as specified by the parameters.
|
||||
:rtype: nx.Graph
|
||||
"""
|
||||
assert allow_euclidean_connections or allow_manhattan_connections
|
||||
if hasattr(coordiniates_or_tiles, 'positions'):
|
||||
coordiniates_or_tiles = coordiniates_or_tiles.positions
|
||||
possible_connections = itertools.combinations(coordiniates_or_tiles, 2)
|
||||
graph = nx.Graph()
|
||||
for a, b in possible_connections:
|
||||
diff = np.linalg.norm(np.asarray(a)-np.asarray(b))
|
||||
if allow_manhattan_connections and allow_euclidean_connections and diff <= np.sqrt(2):
|
||||
graph.add_edge(a, b)
|
||||
elif not allow_manhattan_connections and allow_euclidean_connections and diff == np.sqrt(2):
|
||||
graph.add_edge(a, b)
|
||||
elif allow_manhattan_connections and not allow_euclidean_connections and diff == 1:
|
||||
graph.add_edge(a, b)
|
||||
return graph
|
||||
|
||||
|
||||
class TSPBaseAgent(ABC):
|
||||
|
||||
def __init__(self, state, agent_i, static_problem: bool = True):
|
||||
|
0
environment/assets/agent/__init__.py
Normal file
0
environment/assets/agent/__init__.py
Normal file
@ -57,7 +57,7 @@ class BaseFactory(gym.Env):
|
||||
self.conf = FactoryConfigParser(self._config_file)
|
||||
# Attribute Assignment
|
||||
self.level_filepath = Path(__file__).parent.parent / h.LEVELS_DIR / f'{self.conf.level_name}.txt'
|
||||
self._renderer = None # expensive - don't use it when not required !
|
||||
self._renderer = None # expensive - don't use; unless required !
|
||||
|
||||
parsed_entities = self.conf.load_entities()
|
||||
self.map = LevelParser(self.level_filepath, parsed_entities, self.conf.pomdp_r)
|
||||
@ -173,7 +173,7 @@ class BaseFactory(gym.Env):
|
||||
if not self._renderer: # lazy init
|
||||
from environment.utils.renderer import Renderer
|
||||
global Renderer
|
||||
self._renderer = Renderer(self.map.level_shape, view_radius=self.conf.pomdp_r, fps=20)
|
||||
self._renderer = Renderer(self.map.level_shape, view_radius=self.conf.pomdp_r, fps=10)
|
||||
|
||||
render_entities = self.state.entities.render()
|
||||
if self.conf.pomdp_r:
|
||||
|
@ -1,4 +1,6 @@
|
||||
from os import PathLike
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
import yaml
|
||||
|
||||
@ -20,9 +22,10 @@ class FactoryConfigParser(object):
|
||||
default_actions = [c.MOVE8, c.NOOP]
|
||||
default_observations = [c.WALLS, c.AGENTS]
|
||||
|
||||
def __init__(self, config_path):
|
||||
def __init__(self, config_path, custom_modules_path: Union[None, PathLike] = None):
|
||||
self.config_path = Path(config_path)
|
||||
self.config = yaml.safe_load(config_path.open())
|
||||
self.custom_modules_path = Path(config_path) if custom_modules_path is not None else custom_modules_path
|
||||
self.config = yaml.safe_load(self.config_path.open())
|
||||
self.do_record = False
|
||||
|
||||
def __getattr__(self, item):
|
||||
@ -67,7 +70,12 @@ class FactoryConfigParser(object):
|
||||
entities.extend(x for x in self.entities if x != c.DEFAULTS)
|
||||
|
||||
for entity in entities:
|
||||
try:
|
||||
folder_path = MODULE_PATH if entity not in self.default_entites else DEFAULT_PATH
|
||||
folder_path = (Path(__file__) / '..' / '..' / '..' / folder_path)
|
||||
entity_class = locate_and_import_class(entity, folder_path)
|
||||
except AttributeError:
|
||||
folder_path = self.custom_modules_path
|
||||
entity_class = locate_and_import_class(entity, folder_path)
|
||||
entity_kwargs = self.entities.get(entity, {})
|
||||
entity_symbol = entity_class.symbol if hasattr(entity_class, 'symbol') else None
|
||||
@ -86,11 +94,15 @@ class FactoryConfigParser(object):
|
||||
parsed_actions = list()
|
||||
for action in actions:
|
||||
folder_path = MODULE_PATH if action not in base_env_actions else DEFAULT_PATH
|
||||
try:
|
||||
class_or_classes = locate_and_import_class(action, folder_path)
|
||||
except AttributeError:
|
||||
class_or_classes = locate_and_import_class(action, self.custom_modules_path)
|
||||
try:
|
||||
parsed_actions.extend(class_or_classes)
|
||||
except TypeError:
|
||||
parsed_actions.append(class_or_classes)
|
||||
|
||||
parsed_actions = [x() for x in parsed_actions]
|
||||
|
||||
# Observation
|
||||
@ -114,7 +126,10 @@ class FactoryConfigParser(object):
|
||||
|
||||
for rule in rules:
|
||||
folder_path = MODULE_PATH if rule not in self.default_rules else DEFAULT_PATH
|
||||
try:
|
||||
rule_class = locate_and_import_class(rule, folder_path)
|
||||
except AttributeError:
|
||||
rule_class = locate_and_import_class(rule, self.custom_modules_path)
|
||||
rule_kwargs = self.rules.get(rule, {})
|
||||
rules_classes.update({rule: {'class': rule_class, 'kwargs': rule_kwargs}})
|
||||
return rules_classes
|
||||
|
@ -1,10 +1,9 @@
|
||||
import importlib
|
||||
import itertools
|
||||
|
||||
from collections import defaultdict
|
||||
from pathlib import PurePath, Path
|
||||
from typing import Union, Dict, List
|
||||
|
||||
import networkx as nx
|
||||
import numpy as np
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
@ -210,58 +209,25 @@ def asset_str(agent):
|
||||
return c.AGENT, 'idle'
|
||||
|
||||
|
||||
def points_to_graph(coordiniates_or_tiles, allow_euclidean_connections=True, allow_manhattan_connections=True):
|
||||
"""
|
||||
Given a set of coordinates, this function contructs a non-directed graph, by conncting adjected points.
|
||||
There are three combinations of settings:
|
||||
Allow all neigbors: Distance(a, b) <= sqrt(2)
|
||||
Allow only manhattan: Distance(a, b) == 1
|
||||
Allow only euclidean: Distance(a, b) == sqrt(2)
|
||||
|
||||
|
||||
:param coordiniates_or_tiles: A set of coordinates.
|
||||
:type coordiniates_or_tiles: Tiles
|
||||
:param allow_euclidean_connections: Whether to regard diagonal adjected cells as neighbors
|
||||
:type: bool
|
||||
:param allow_manhattan_connections: Whether to regard directly adjected cells as neighbors
|
||||
:type: bool
|
||||
|
||||
:return: A graph with nodes that are conneceted as specified by the parameters.
|
||||
:rtype: nx.Graph
|
||||
"""
|
||||
assert allow_euclidean_connections or allow_manhattan_connections
|
||||
if hasattr(coordiniates_or_tiles, 'positions'):
|
||||
coordiniates_or_tiles = coordiniates_or_tiles.positions
|
||||
possible_connections = itertools.combinations(coordiniates_or_tiles, 2)
|
||||
graph = nx.Graph()
|
||||
for a, b in possible_connections:
|
||||
diff = np.linalg.norm(np.asarray(a)-np.asarray(b))
|
||||
if allow_manhattan_connections and allow_euclidean_connections and diff <= np.sqrt(2):
|
||||
graph.add_edge(a, b)
|
||||
elif not allow_manhattan_connections and allow_euclidean_connections and diff == np.sqrt(2):
|
||||
graph.add_edge(a, b)
|
||||
elif allow_manhattan_connections and not allow_euclidean_connections and diff == 1:
|
||||
graph.add_edge(a, b)
|
||||
return graph
|
||||
|
||||
|
||||
def locate_and_import_class(class_name, folder_path: Union[str, PurePath] = ''):
|
||||
"""Locate an object by name or dotted path, importing as necessary."""
|
||||
import sys
|
||||
sys.path.append("..")
|
||||
folder_path = Path(folder_path)
|
||||
module_paths = [x for x in folder_path.rglob('*.py') if x.is_file() and '__init__' not in x.name]
|
||||
folder_path = Path(folder_path).resolve()
|
||||
module_paths = [x.resolve() for x in folder_path.rglob('*.py') if x.is_file() and '__init__' not in x.name]
|
||||
# possible_package_path = folder_path / '__init__.py'
|
||||
# package = str(possible_package_path) if possible_package_path.exists() else None
|
||||
all_found_modules = list()
|
||||
package_pos = next(idx for idx, x in enumerate(Path(__file__).resolve().parts) if x == 'environment')
|
||||
for module_path in module_paths:
|
||||
mod = importlib.import_module('.'.join([x.replace('.py', '') for x in module_path.parts]))
|
||||
module_parts = [x.replace('.py', '') for idx, x in enumerate(module_path.parts) if idx >= package_pos]
|
||||
mod = importlib.import_module('.'.join(module_parts))
|
||||
all_found_modules.extend([x for x in dir(mod) if not(x.startswith('__') or len(x) < 2 or x.isupper())
|
||||
and x not in ['Entity', 'NamedTuple', 'List', 'Rule', 'Union', 'random', 'Floor'
|
||||
'TickResult', 'ActionResult', 'Action', 'Agent', 'deque',
|
||||
'BoundEntityMixin', 'RenderEntity', 'TemplateRule', 'defaultdict',
|
||||
'is_move', 'Objects', 'PositionMixin', 'IsBoundMixin', 'EnvObject',
|
||||
'EnvObjects',]])
|
||||
'EnvObjects']])
|
||||
try:
|
||||
model_class = mod.__getattribute__(class_name)
|
||||
return model_class
|
||||
|
@ -191,6 +191,9 @@ class RayCaster:
|
||||
self.ray_targets = self.build_ray_targets()
|
||||
self.obs_shape_cube = np.array([self.pomdp_r, self.pomdp_r])
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.__class__.__name__}({self.agent.name})'
|
||||
|
||||
def build_ray_targets(self):
|
||||
north = np.array([0, -1])*self.pomdp_r
|
||||
thetas = [np.deg2rad(deg) for deg in np.linspace(-self.degs // 2, self.degs // 2, self.n_rays)[::-1]]
|
||||
@ -202,12 +205,9 @@ class RayCaster:
|
||||
rot_M = np.unique(np.round(rot_M @ north), axis=0)
|
||||
return rot_M.astype(int)
|
||||
|
||||
@staticmethod
|
||||
def ray_block_cache(cache_dict, key, callback, ents):
|
||||
def ray_block_cache(self, cache_dict, key, callback, ents):
|
||||
if key not in cache_dict:
|
||||
cache_dict[key] = callback()
|
||||
if any(True for e in ents.pos_dict[key] if e.is_blocking_light) and not cache_dict[key]:
|
||||
print()
|
||||
return cache_dict[key]
|
||||
|
||||
def visible_entities(self, entities):
|
||||
@ -222,14 +222,23 @@ class RayCaster:
|
||||
entities_hit = entities.pos_dict[(x, y)]
|
||||
hits = self.ray_block_cache(cache_blocking,
|
||||
(x, y),
|
||||
lambda: any(True for e in entities_hit if e.is_blocking_light),
|
||||
lambda: any(e.is_blocking_light for e in entities_hit),
|
||||
entities)
|
||||
|
||||
diag_hits = all([
|
||||
try:
|
||||
d = next(x for x in entities_hit if 'Door' in x.name)
|
||||
if d.pos in entities.pos_dict.keys():
|
||||
if d.is_closed and not entities.pos_dict[d.pos]:
|
||||
print()
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
diag_hits = any([
|
||||
self.ray_block_cache(
|
||||
cache_blocking,
|
||||
key,
|
||||
lambda: all(False for e in entities.pos_dict[key] if not e.is_blocking_light),
|
||||
# lambda: all(False for e in entities.pos_dict[key] if not e.is_blocking_light),
|
||||
lambda: any(e.is_blocking_light for e in entities.pos_dict[key]),
|
||||
entities)
|
||||
for key in ((x, y-cy), (x-cx, y))
|
||||
]) if (cx != 0 and cy != 0) else False
|
||||
@ -238,13 +247,6 @@ class RayCaster:
|
||||
if hits or diag_hits:
|
||||
break
|
||||
rx, ry = x, y
|
||||
try:
|
||||
d = next(x for x in visible if 'Door' in x.name)
|
||||
v = [x for x in visible if tuple(np.subtract(x.pos, d.pos)) in [(1, 0), (0, 1), (-1, 0), (0, -1)] and x.name.startswith('Floor')]
|
||||
if len(v) > 2:
|
||||
pass
|
||||
except StopIteration:
|
||||
pass
|
||||
return visible
|
||||
|
||||
def get_rays(self):
|
||||
|
@ -3,12 +3,12 @@ import sys
|
||||
from pathlib import Path
|
||||
from collections import deque
|
||||
from itertools import product
|
||||
|
||||
import numpy as np
|
||||
import pygame
|
||||
from typing import Tuple, Union
|
||||
import time
|
||||
|
||||
import torch
|
||||
|
||||
from environment.utils.render import RenderEntity
|
||||
|
||||
AGENT: str = 'agent'
|
||||
@ -133,7 +133,8 @@ class Renderer:
|
||||
pygame.display.flip()
|
||||
self.clock.tick(self.fps)
|
||||
rgb_obs = pygame.surfarray.array3d(self.screen)
|
||||
return torch.from_numpy(rgb_obs).permute(2, 0, 1)
|
||||
return np.transpose(rgb_obs, (2, 0, 1))
|
||||
# return torch.from_numpy(rgb_obs).permute(2, 0, 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -29,8 +29,9 @@ Entities:
|
||||
Agents:
|
||||
Wolfgang:
|
||||
Actions:
|
||||
- Move8
|
||||
- DoorUse
|
||||
- Noop
|
||||
- Noop
|
||||
- Noop
|
||||
- CleanUp
|
||||
Observations:
|
||||
- Self
|
||||
|
@ -1,12 +0,0 @@
|
||||
numpy~=1.24.3
|
||||
tqdm~=4.60.0
|
||||
pandas~=1.2.3
|
||||
seaborn~=0.12.2
|
||||
matplotlib>=3.3.4
|
||||
pygame~=2.0.1
|
||||
networkx>=2.6.3
|
||||
simplejson~=3.17.2
|
||||
PyYAML~=5.3.1
|
||||
natsort~=7.1.1
|
||||
torch~=1.10.0
|
||||
gymnasium~=0.28.1
|
21
setup.py
21
setup.py
@ -4,29 +4,34 @@ this_directory = Path(__file__).parent
|
||||
long_description = (this_directory / "README.md").read_text()
|
||||
|
||||
|
||||
setup(name='Marl-Neon-Grid',
|
||||
version='0.1.4.4',
|
||||
description='A collection of MARL gridworlds to study coordination and cooperation.',
|
||||
author='Robert Müller',
|
||||
setup(name='Marl-Factory-Grid',
|
||||
version='0.0.5',
|
||||
description='A framework to research MARL agents in various setings.',
|
||||
author='Steffen Illium',
|
||||
author_email='steffen.illium@ifi.lmu.de',
|
||||
url='https://github.com/romue404/marl-neon-grid',
|
||||
url='https://github.com/illiumst/marl-factory-grid/import',
|
||||
license='MIT',
|
||||
keywords=[
|
||||
'artificial intelligence',
|
||||
'pytorch',
|
||||
'multiagent reinforcement learning',
|
||||
'simulation'
|
||||
'simulation',
|
||||
'emergence',
|
||||
'gymnasium',
|
||||
'environment',
|
||||
|
||||
],
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Intended Audience :: Developers',
|
||||
'Topic :: Scientific/Engineering :: Artificial Intelligence',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
],
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
packages=find_packages(exclude=['examples']),
|
||||
include_package_data=True,
|
||||
install_requires=['numpy', 'pygame>=2.0', 'numba>=0.56', 'gymnasium>=0.26', 'seaborn', 'pandas']
|
||||
install_requires=['numpy', 'pygame>=2.0', 'numba>=0.56', 'gymnasium>=0.26', 'seaborn', 'pandas',
|
||||
'pyyaml', 'networkx', 'torch', 'tqdm']
|
||||
)
|
Reference in New Issue
Block a user