Doors now Working,

Pypi install adjustments.
This commit is contained in:
Steffen Illium
2023-06-16 09:58:28 +02:00
parent 45cb83746c
commit b4326b514c
12 changed files with 108 additions and 96 deletions

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):

@ -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

@ -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']
)