diff --git a/.gitignore b/.gitignore index 15fb17b133b4c707651b8411b4934d2734968688..a003eda10b9827f972a29ecccbe9b67c3cfa7de1 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,4 @@ ENV/ images/test/ +test_save.dat diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c722f3c334d412eb2796cff2d46e4e01f063e18d..885f2a32267882160943c6fe20f5b199c244de25 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,6 +25,7 @@ tests: - apt update - apt install -y libgl1-mesa-glx xvfb - pip install tox + - apt install -y graphviz xdg-utils - xvfb-run -s "-screen 0 800x600x24" tox build_and_deploy_docs: diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f552f7626cfffca9a4422cb9337c9cdc27d83867..7ae26bcc3d4e0f4a5cbbcfafd055e2c084a42345 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -79,7 +79,7 @@ Ready to contribute? Here's how to set up `flatland` for local development. 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: - $ flake8 flatland tests + $ flake8 flatland tests examples $ python setup.py test or py.test $ tox @@ -125,4 +125,4 @@ $ bumpversion patch # possible: major / minor / patch $ git push $ git push --tags -Travis will then deploy to PyPI if tests pass. (To be configured properly by Mohanty) \ No newline at end of file +Travis will then deploy to PyPI if tests pass. (To be configured properly by Mohanty) diff --git a/MANIFEST.in b/MANIFEST.in index 965b2dda7db7c49f68857dc3aea9af37e30a745e..30bf97cf0de32bad7e630a80e2b977eb85d48911 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,7 @@ include CONTRIBUTING.rst include HISTORY.rst include LICENSE include README.rst +include requirements_dev.txt recursive-include tests * recursive-exclude * __pycache__ diff --git a/Makefile b/Makefile index 42a8a430671f9464101ba0799bb6ac4e4f35be20..69ad1b42fd51ef9ec9420f5473dc8acef5468572 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ clean-test: ## remove test and coverage artifacts rm -fr .pytest_cache lint: ## check style with flake8 - flake8 flatland tests + flake8 flatland tests examples test: ## run tests quickly with the default Python echo "$$DISPLAY" @@ -72,6 +72,7 @@ docs: ## generate Sphinx HTML documentation, including API docs sphinx-apidoc -o docs/ flatland $(MAKE) -C docs clean $(MAKE) -C docs html + pydeps --no-config --noshow flatland -o docs/_build/html/flatland.svg $(BROWSER) docs/_build/html/index.html servedocs: docs ## compile the docs watching for changes diff --git a/README.rst b/README.rst index b4d3193c1b4fb55327216164c602d75cc92e3fdb..11b35adc127b2b5b8b22c55fd0f494871aee56cd 100644 --- a/README.rst +++ b/README.rst @@ -92,6 +92,10 @@ flatland ======== TODO: explain the interface here +Module Dependencies +=================== +.. image:: flatland.svg + Authors -------- @@ -102,6 +106,7 @@ Authors * Erik Nygren <erik.nygren@sbb.ch> * Adrian Egli <adrian.egli@sbb.ch> * Vaibhav Agrawal <theinfamouswayne@gmail.com> +* Christian Eichenberger <christian.markus.eichenberger@sbb.ch> <please fill yourself in> diff --git a/examples/play_model.py b/examples/play_model.py index bbbcfffdccf4493bc08da013027f4fdc31ca5aa7..34c6aadfeefd44771fd335e2957e1fbd0b2f740f 100644 --- a/examples/play_model.py +++ b/examples/play_model.py @@ -1,12 +1,14 @@ -from flatland.envs.rail_env import RailEnv -from flatland.envs.generators import complex_rail_generator -from flatland.utils.rendertools import RenderTool -# from flatland.baselines.dueling_double_dqn import Agent -from collections import deque # import torch import random -import numpy as np import time +# from flatland.baselines.dueling_double_dqn import Agent +from collections import deque + +import numpy as np + +from flatland.envs.generators import complex_rail_generator +from flatland.envs.rail_env import RailEnv +from flatland.utils.rendertools import RenderTool class Player(object): @@ -25,7 +27,7 @@ class Player(object): self.done_window = deque(maxlen=100) self.scores = [] self.dones_list = [] - self.action_prob = [0]*4 + self.action_prob = [0] * 4 # Removing refs to a real agent for now. # self.agent = Agent(self.state_size, self.action_size, "FC", 0) @@ -35,7 +37,7 @@ class Player(object): self.iFrame = 0 self.tStart = time.time() - + # Reset environment # self.obs = self.env.reset() self.env.obs_builder.reset() @@ -70,7 +72,7 @@ class Player(object): # Environment step - pass the agent actions to the environment, # retrieve the response - observations, rewards, dones next_obs, all_rewards, done, _ = self.env.step(self.action_dict) - + for handle in env.get_agent_handles(): norm = max(1, max_lt(next_obs[handle], np.inf)) next_obs[handle] = np.clip(np.array(next_obs[handle]) / norm, -1, 1) @@ -79,8 +81,8 @@ class Player(object): if False: for handle in self.env.get_agent_handles(): self.agent.step(self.obs[handle], self.action_dict[handle], - all_rewards[handle], next_obs[handle], done[handle], - train=False) + all_rewards[handle], next_obs[handle], done[handle], + train=False) self.score += all_rewards[handle] self.iFrame += 1 @@ -96,7 +98,7 @@ def max_lt(seq, val): None is returned if seq was empty or all items in seq were >= val. """ - idx = len(seq)-1 + idx = len(seq) - 1 while idx >= 0: if seq[idx] < val and seq[idx] >= 0: return seq[idx] @@ -135,16 +137,16 @@ def main(render=True, delay=0.0, n_trials=3, n_steps=50, sGL="QT"): oPlayer.step() if render: env_renderer.renderEnv(show=True, frames=True, iEpisode=trials, iStep=step, - action_dict=oPlayer.action_dict) + action_dict=oPlayer.action_dict) # time.sleep(10) if delay > 0: time.sleep(delay) - + def main_old(render=True, delay=0.0): ''' DEPRECATED main which drives agent directly Please use the new main() which creates a Player object which is also used by the Editor. - Please fix any bugs in main() and Player rather than here. + Please fix any bugs in main() and Player rather than here. Will delete this one shortly. ''' @@ -169,7 +171,7 @@ def main_old(render=True, delay=0.0): done_window = deque(maxlen=100) scores = [] dones_list = [] - action_prob = [0]*4 + action_prob = [0] * 4 # Real Agent # state_size = 105 @@ -183,7 +185,7 @@ def main_old(render=True, delay=0.0): None is returned if seq was empty or all items in seq were >= val. """ - idx = len(seq)-1 + idx = len(seq) - 1 while idx >= 0: if seq[idx] < val and seq[idx] >= 0: return seq[idx] @@ -196,7 +198,8 @@ def main_old(render=True, delay=0.0): # Reset environment obs = env.reset() - env_renderer.set_new_rail() + if render: + env_renderer.set_new_rail() for a in range(env.get_num_agents()): norm = max(1, max_lt(obs[a], np.inf)) @@ -252,25 +255,25 @@ def main_old(render=True, delay=0.0): print(('\rTraining {} Agents.\tEpisode {}\tAverage Score: {:.0f}\tDones: {:.2f}%' + '\tEpsilon: {:.2f} \t Action Probabilities: \t {}').format( - env.get_num_agents(), - trials, - np.mean(scores_window), - 100 * np.mean(done_window), - eps, action_prob/np.sum(action_prob)), + env.get_num_agents(), + trials, + np.mean(scores_window), + 100 * np.mean(done_window), + eps, action_prob / np.sum(action_prob)), end=" ") if trials % 100 == 0: tNow = time.time() rFps = iFrame / (tNow - tStart) print(('\rTraining {} Agents.\tEpisode {}\tAverage Score: {:.0f}\tDones: {:.2f}%' + '\tEpsilon: {:.2f} fps: {:.2f} \t Action Probabilities: \t {}').format( - env.get_num_agents(), - trials, - np.mean(scores_window), - 100 * np.mean(done_window), - eps, rFps, action_prob / np.sum(action_prob))) + env.get_num_agents(), + trials, + np.mean(scores_window), + 100 * np.mean(done_window), + eps, rFps, action_prob / np.sum(action_prob))) # torch.save(agent.qnetwork_local.state_dict(), # '../flatland/baselines/Nets/avoid_checkpoint' + str(trials) + '.pth') - action_prob = [1]*4 + action_prob = [1] * 4 if __name__ == "__main__": diff --git a/examples/qt2.py b/examples/qt2.py index 6074106523c7fbe503f79b6bc7604f055dc27b35..ee3ea0cd123a3e6d0b1fc970eced219ed8503203 100644 --- a/examples/qt2.py +++ b/examples/qt2.py @@ -1,9 +1,8 @@ - - import sys + from PyQt5 import QtSvg -from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QGridLayout, QWidget from PyQt5.QtCore import Qt, QByteArray +from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QGridLayout, QWidget from flatland.utils import svg @@ -75,4 +74,3 @@ window = MainWindow() window.show() app.exec_() - diff --git a/examples/temporary_example.py b/examples/temporary_example.py index 1f3504f221d59d0205974bb135c2237364a22e07..862369411056d87d411c3e173bd479e9a7e93e01 100644 --- a/examples/temporary_example.py +++ b/examples/temporary_example.py @@ -1,11 +1,9 @@ import random -import numpy as np -import matplotlib.pyplot as plt -from flatland.envs.rail_env import * -from flatland.envs.generators import * -from flatland.envs.observations import TreeObsForRailEnv -from flatland.utils.rendertools import * +from flatland.envs.generators import random_rail_generator +from flatland.envs.rail_env import RailEnv +from flatland.utils.rendertools import RenderTool +import numpy as np random.seed(0) np.random.seed(0) @@ -94,7 +92,7 @@ env = RailEnv(width=7, # print(env.obs_builder.distance_map[0, :, :, i]) # Print the observation vector for agent 0 -obs, all_rewards, done, _ = env.step({0:0}) +obs, all_rewards, done, _ = env.step({0: 0}) for i in range(env.get_num_agents()): env.obs_builder.util_print_obs_subtree(tree=obs[i], num_features_per_node=5) @@ -113,6 +111,7 @@ for step in range(100): while i < len(cmds): if cmds[i] == 'q': import sys + sys.exit() elif cmds[i] == 's': obs, all_rewards, done, _ = env.step(action_dict) @@ -120,9 +119,9 @@ for step in range(100): print("Rewards: ", all_rewards, " [done=", done, "]") else: agent_id = int(cmds[i]) - action = int(cmds[i+1]) + action = int(cmds[i + 1]) action_dict[agent_id] = action - i = i+1 + i = i + 1 i += 1 env_renderer.renderEnv(show=True) diff --git a/examples/training_navigation.py b/examples/training_navigation.py index cabb655e3eb2bc2d12d908559b0102b072163052..85f9531b8820139e5559081feee4a93c4e01ac6c 100644 --- a/examples/training_navigation.py +++ b/examples/training_navigation.py @@ -1,11 +1,15 @@ -from flatland.envs.rail_env import * -from flatland.envs.generators import * -from flatland.envs.observations import TreeObsForRailEnv -from flatland.utils.rendertools import * -from flatland.baselines.dueling_double_dqn import Agent -from collections import deque -import torch, random +import random import time +from collections import deque + +import numpy as np +import torch + +from flatland.baselines.dueling_double_dqn import Agent +from flatland.envs.generators import complex_rail_generator +from flatland.envs.rail_env import RailEnv +from flatland.utils.rendertools import RenderTool + random.seed(1) np.random.seed(1) @@ -190,25 +194,34 @@ for trials in range(1, n_trials + 1): dones_list.append((np.mean(done_window))) print( - '\rTraining {} Agents.\tEpisode {}\tAverage Score: {:.0f}\tDones: {:.2f}%\tEpsilon: {:.2f} \t Action Probabilities: \t {}'.format( + '\rTraining {} Agents.\t' + + 'Episode {}\t' + + 'Average Score: {:.0f}\t' + + 'Dones: {:.2f}%\t' + + 'Epsilon: {:.2f} \t ' + + 'Action Probabilities: \t ' + + '{}'.format( env.get_num_agents(), trials, - np.mean( - scores_window), - 100 * np.mean( - done_window), + np.mean(scores_window), + 100 * np.mean(done_window), eps, action_prob / np.sum(action_prob)), end=" ") if trials % 100 == 0: print( - '\rTraining {} Agents.\tEpisode {}\tAverage Score: {:.0f}\tDones: {:.2f}%\tEpsilon: {:.2f} \t Action Probabilities: \t {}'.format( + '\rTraining {} Agents.\t' + + 'Episode {}\t' + + 'Average Score: {:.0f}\t' + + 'Dones: {:.2f}%\t' + + 'Epsilon: {:.2f} \t ' + + 'Action Probabilities: \t ' + + '{}'.format( env.get_num_agents(), trials, - np.mean( - scores_window), - 100 * np.mean( - done_window), - eps, action_prob / np.sum(action_prob))) + np.mean(scores_window), + 100 * np.mean(done_window), + eps, + action_prob / np.sum(action_prob))) torch.save(agent.qnetwork_local.state_dict(), '../flatland/baselines/Nets/avoid_checkpoint' + str(trials) + '.pth') action_prob = [1] * 4 diff --git a/flatland/envs/observations.py b/flatland/envs/observations.py index 651d83520ee1f492ea71ba6ddf82cfa5f9093964..fa77b58a53ef63767c2690edc782b6699f6c8459 100644 --- a/flatland/envs/observations.py +++ b/flatland/envs/observations.py @@ -492,8 +492,11 @@ class GlobalObsForRailEnv(ObservationBuilder): self.rail_obs = np.zeros((self.env.height, self.env.width, 16)) for i in range(self.rail_obs.shape[0]): for j in range(self.rail_obs.shape[1]): - self.rail_obs[i, j] = np.array( - list(f'{self.env.rail.get_transitions((i, j)):016b}')).astype(int) + bitlist = [int(digit) for digit in bin(self.env.rail.get_transitions((i, j)))[2:]] + bitlist = [0] * (16 - len(bitlist)) + bitlist + self.rail_obs[i, j] = np.array(bitlist) + # self.rail_obs[i, j] = np.array( + # list(f'{self.env.rail.get_transitions((i, j)):016b}')).astype(int) # self.targets = np.zeros(self.env.height, self.env.width) # for target_pos in self.env.agents_target: diff --git a/make_docs.py b/make_docs.py index 7ccbdb736b9b53743b58cfd985538705c5e79f08..8cc1124a6fe624fe5afff416450a0a5d30d654ca 100644 --- a/make_docs.py +++ b/make_docs.py @@ -25,5 +25,6 @@ os.environ["SPHINXPROJ"] = "flatland" os.chdir('docs') subprocess.call(['python', '-msphinx', '-M', 'clean', '.', '_build']) subprocess.call(['python', '-msphinx', '-M', 'html', '.', '_build']) +subprocess.call(['python', '-mpydeps', '../flatland', '-o', '_build/html/flatland.svg']) browser('_build/html/index.html') diff --git a/requirements_dev.txt b/requirements_dev.txt index 08fcc9d17a409f4df71b0b67e037dde5ae042f8a..4b288cee88f4231f330c51f70b1cd9c9f9d05389 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -2,6 +2,7 @@ bumpversion==0.5.3 wheel==0.32.1 watchdog==0.9.0 flake8==3.5.0 +pydeps==1.7.2 tox==3.5.2 coverage==4.5.1 Sphinx==1.8.1 diff --git a/setup.py b/setup.py index 96afbe85dc99aa47533936d2a001b9d74717ec58..d14070351559b5e59ed677751b70c20891dbc2f4 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """The setup script.""" - +import os from setuptools import setup, find_packages with open('README.rst') as readme_file: @@ -11,11 +11,19 @@ with open('README.rst') as readme_file: with open('HISTORY.rst') as history_file: history = history_file.read() -requirements = ['Click>=6.0', ] - -setup_requirements = ['pytest-runner', ] - -test_requirements = ['pytest', ] +# Gather requirements from requirements_dev.txt +# TODO : We could potentially split up the test/dev dependencies later +install_reqs = [] +requirements_path = 'requirements_dev.txt' +with open(requirements_path, 'r') as f: + install_reqs += [ + s for s in [ + line.strip(' \n') for line in f + ] if not s.startswith('#') and s != '' + ] +requirements = install_reqs +setup_requirements = install_reqs +test_requirements = install_reqs setup( author="S.P. Mohanty", diff --git a/tests/test_rendertools.py b/tests/test_rendertools.py index 7d3ddf63226b55217edd28bf25aed0307377433b..8204a305328df746a772d034f3c763c848cceb93 100644 --- a/tests/test_rendertools.py +++ b/tests/test_rendertools.py @@ -4,14 +4,14 @@ Tests for `flatland` package. """ -from flatland.envs.rail_env import RailEnv, random_rail_generator -import numpy as np import sys import matplotlib.pyplot as plt +import numpy as np import flatland.utils.rendertools as rt from flatland.envs.observations import TreeObsForRailEnv +from flatland.envs.rail_env import RailEnv, random_rail_generator def checkFrozenImage(oRT, sFileImage, resave=False): diff --git a/tox.ini b/tox.ini index e97f8ab022c69cf7ff7aa65d782dddafbecfc90f..6dd011aadeb2e7ba802ff692278aa763fb665f10 100644 --- a/tox.ini +++ b/tox.ini @@ -8,12 +8,12 @@ python = [flake8] max-line-length = 120 -ignore = E121 E126 E123 E128 E133 E226 E241 E242 E704 W291 W293 W391 W503 W504 W505 +ignore = E121 E126 E123 E128 E133 E226 E241 E242 E704 W291 W293 W391 W503 W504 W505 [testenv:flake8] basepython = python deps = flake8 -commands = flake8 flatland +commands = flake8 flatland tests examples [testenv:docs] basepython = python @@ -23,12 +23,15 @@ commands = make docs [testenv:coverage] basepython = python whitelist_externals = make -commands = make coverage +commands = + pip install -U pip + pip install -r requirements_dev.txt + make coverage [testenv] whitelist_externals = xvfb-run - sh - pip + sh + pip setenv = PYTHONPATH = {toxinidir} deps = @@ -38,6 +41,7 @@ deps = ; -r{toxinidir}/requirements.txt commands = pip install -U pip + pip install -r requirements_dev.txt sh -c 'echo DISPLAY: $DISPLAY' xvfb-run -a py.test --basetemp={envtmpdir}