mirror of
				https://github.com/illiumst/marl-factory-grid.git
				synced 2025-10-30 20:27:27 +01: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,8 +70,13 @@ class FactoryConfigParser(object): | ||||
|         entities.extend(x for x in self.entities if x != c.DEFAULTS) | ||||
|  | ||||
|         for entity in entities: | ||||
|             folder_path = MODULE_PATH if entity not in self.default_entites else DEFAULT_PATH | ||||
|             entity_class = locate_and_import_class(entity, folder_path) | ||||
|             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 | ||||
|             entity_classes.update({entity: {'class': entity_class, 'kwargs': entity_kwargs, 'symbol': entity_symbol}}) | ||||
| @@ -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 | ||||
|                 class_or_classes = locate_and_import_class(action, folder_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 | ||||
|             rule_class = locate_and_import_class(rule, folder_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 | ||||
| @@ -269,4 +235,4 @@ def locate_and_import_class(class_name, folder_path: Union[str, PurePath] = ''): | ||||
|             continue | ||||
|     raise AttributeError(f'Class "{class_name}" was not found!!!"\n' | ||||
|                          f'Check the {folder_path.name} name.\n' | ||||
|                          f'Possible Options are:\n{set(all_found_modules)}') | ||||
|                          f'Possible Options are:\n{set(all_found_modules)}') | ||||
|   | ||||
| @@ -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 | ||||
							
								
								
									
										23
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								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
	 Steffen Illium
					Steffen Illium