From 46bbdb6a8c0ea9efa2589fb87ddd122beeb6ee4a Mon Sep 17 00:00:00 2001 From: gregSchwartz Date: Tue, 2 Jul 2019 16:41:47 -0400 Subject: [PATCH] init --- .flake8 | 2 + .gitignore | 120 ++ .travis.yml | 42 + README.md | 64 + __init__.py | 0 docker/Dockerfile | 42 + docker/build.sh | 1 + docker/readme.md | 34 + environment.yml | 16 + images/cleanup.png | Bin 0 -> 22030 bytes images/harvest.png | Bin 0 -> 203885 bytes images/schelling.png | Bin 0 -> 76671 bytes models/__init__.py | 0 models/conv_to_fc_net.py | 47 + models/conv_to_fc_net_actions.py | 60 + models/conv_to_fc_net_actions_no_lstm.py | 60 + models/conv_to_fc_net_no_lstm.py | 47 + ray_autoscale.yaml | 102 ++ requirements.txt | 49 + requirements_autoscale.txt | 11 + rollout.py | 126 ++ run_scripts/README.md | 24 + run_scripts/train_a3c_equity.py | 229 ++++ run_scripts/train_a3c_influence.py | 230 ++++ run_scripts/train_a3c_moa.py | 213 ++++ run_scripts/train_baseline.py | 183 +++ run_scripts/train_baseline_a3c.py | 195 +++ run_scripts/train_baseline_a3c_actions.py | 209 ++++ run_scripts/train_baseline_dqn.py | 166 +++ run_scripts/train_baseline_dqn_actions.py | 169 +++ setup.py | 7 + social_dilemmas/__init__.py | 0 social_dilemmas/constants.py | 93 ++ social_dilemmas/envs/__init__.py | 0 social_dilemmas/envs/agent.py | 246 ++++ social_dilemmas/envs/cleanup.py | 168 +++ social_dilemmas/envs/harvest.py | 128 ++ social_dilemmas/envs/map_env.py | 764 ++++++++++++ tests/__init__.py | 0 tests/cleanup_trajectory.mp4 | Bin 0 -> 29307 bytes tests/test_envs.py | 1313 +++++++++++++++++++++ tests/test_rollout.py | 20 + utility_funcs.py | 113 ++ visuallizer_rllib.py | 261 ++++ 44 files changed, 5554 insertions(+) create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 README.md create mode 100644 __init__.py create mode 100644 docker/Dockerfile create mode 100644 docker/build.sh create mode 100644 docker/readme.md create mode 100644 environment.yml create mode 100644 images/cleanup.png create mode 100644 images/harvest.png create mode 100644 images/schelling.png create mode 100644 models/__init__.py create mode 100644 models/conv_to_fc_net.py create mode 100644 models/conv_to_fc_net_actions.py create mode 100644 models/conv_to_fc_net_actions_no_lstm.py create mode 100644 models/conv_to_fc_net_no_lstm.py create mode 100644 ray_autoscale.yaml create mode 100644 requirements.txt create mode 100644 requirements_autoscale.txt create mode 100644 rollout.py create mode 100644 run_scripts/README.md create mode 100644 run_scripts/train_a3c_equity.py create mode 100644 run_scripts/train_a3c_influence.py create mode 100644 run_scripts/train_a3c_moa.py create mode 100644 run_scripts/train_baseline.py create mode 100644 run_scripts/train_baseline_a3c.py create mode 100644 run_scripts/train_baseline_a3c_actions.py create mode 100644 run_scripts/train_baseline_dqn.py create mode 100644 run_scripts/train_baseline_dqn_actions.py create mode 100644 setup.py create mode 100644 social_dilemmas/__init__.py create mode 100644 social_dilemmas/constants.py create mode 100644 social_dilemmas/envs/__init__.py create mode 100644 social_dilemmas/envs/agent.py create mode 100644 social_dilemmas/envs/cleanup.py create mode 100644 social_dilemmas/envs/harvest.py create mode 100644 social_dilemmas/envs/map_env.py create mode 100644 tests/__init__.py create mode 100644 tests/cleanup_trajectory.mp4 create mode 100644 tests/test_envs.py create mode 100644 tests/test_rollout.py create mode 100644 utility_funcs.py create mode 100644 visuallizer_rllib.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..aad4f902 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 101 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4883e0e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,120 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +.idea/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# project specific +/videos +/videos/* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..f0a9542f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +language: python + +cache: pip + +python: + - "3.5" + +os: linux + +dist: trusty + +sudo: required + +before_install: + - sudo apt-get update + # Setup conda (needed for opencv, ray dependency) + # WARNING: enforces py3.5 + - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; + - bash miniconda.sh -b -p $HOME/miniconda + - export PATH="$HOME/miniconda/bin:$PATH" + - hash -r + - conda config --set always_yes yes --set changeps1 no + - conda update -q conda + - conda info -a + - python -V + + # Set up requirements for running tests + - conda env create -f environment.yml + - source activate causal + +install: + - pip install flake8 . + - pip install pytest + +before_script: + - flake8 --version + - flake8 --show-source + +script: + - python setup.py install + - python -m pytest + diff --git a/README.md b/README.md new file mode 100644 index 00000000..ed635ff4 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +[![Build Status](https://travis-ci.com/eugenevinitsky/sequential_social_dilemma_games.svg?branch=master)](https://travis-ci.com/eugenevinitsky/sequential_social_dilemma_games) + +# Sequential Social Dilemma Games +This repo is an open-source implementation of DeepMind's Sequential Social Dilemma (SSD) multi-agent game-theoretic environments [[1]](https://arxiv.org/abs/1702.03037). SSDs can be thought of as analogous to spatially and temporally extended Prisoner's Dilemma-like games. The reward structure poses a dilemma because individual short-term optimal strategies lead to poor long-term outcomes for the group. + +The implemented environments are structured to be compatible with OpenAIs gym environments (https://github.com/openai/gym) as well as RLlib's Multiagent Environment (https://github.com/ray-project/ray/blob/master/python/ray/rllib/env/multi_agent_env.py) + +## Implemented Games + +* **Cleanup**: A public goods dilemma in which agents get a reward for consuming apples, but must use a cleaning beam to clean a river in order for apples to grow. While an agent is cleaning the river, other agents can exploit it by consuming the apples that appear. + +Image of the cleanup game + +* **Harvest**: A tragedy-of-the-commons dilemma in which apples regrow at a rate that depends on the amount of nearby apples. If individual agents employ an exploitative strategy by greedily consuming too many apples, the collective reward of all agents is reduced. + +Image of the Harvest game + +Schelling diagrams for Harvest and Cleanup + +The above plot shows the empirical Schelling diagrams for both Cleanup (A) and Harvest (B) (from [[2]](https://arxiv.org/abs/1803.08884)). These diagrams show the payoff that an individual agent can expect if it follows a defecting/exploitative strategy (red) vs a cooperative strategy (blue), given the number of other agents that are cooperating. We can see that an individual agent can almost always greedily benefit from detecting, but the more agents that defect, the worse the outcomes for all agents. + +## Relevant papers + +1. Leibo, J. Z., Zambaldi, V., Lanctot, M., Marecki, J., & Graepel, T. (2017). [Multi-agent reinforcement learning in sequential social dilemmas](https://arxiv.org/abs/1702.03037). In Proceedings of the 16th Conference on Autonomous Agents and MultiAgent Systems (pp. 464-473). + +2. Hughes, E., Leibo, J. Z., Phillips, M., Tuyls, K., Dueñez-Guzman, E., Castañeda, A. G., Dunning, I., Zhu, T., McKee, K., Koster, R., Tina Zhu, Roff, H., Graepel, T. (2018). [Inequity aversion improves cooperation in intertemporal social dilemmas](https://arxiv.org/abs/1803.08884). In Advances in Neural Information Processing Systems (pp. 3330-3340). + +3. Jaques, N., Lazaridou, A., Hughes, E., Gulcehre, C., Ortega, P. A., Strouse, D. J., Leibo, J. Z. & de Freitas, N. (2018). [Intrinsic Social Motivation via Causal Influence in Multi-Agent RL](https://arxiv.org/abs/1810.08647). arXiv preprint arXiv:1810.08647. + + +# Setup instructions +Run `python setup.py develop` +Then, activate your environment by running `source activate causal`. + +To then set up the branch of Ray on which we have built the causal influence code, clone the repo to your desired folder: +`git clone https://github.com/natashamjaques/ray.git`. + +Next, go to the rllib folder: +` cd ray/python/ray/rllib ` and run the script `python setup-rllib-dev.py`. This will copy the rllib folder into the pip install of Ray and allow you to use the version of RLlib that is in your local folder by creating a softlink. + +# Tests +Tests are located in the test folder and can be run individually or run by running `python -m pytest`. Many of the less obviously defined rules for the games can be understood by reading the tests, each of which outline some aspect of the game. + +# Constructing new environments +Every environment that subclasses MapEnv probably needs to implement the following methods + +``` + def custom_reset(self): + """Reset custom elements of the map. For example, spawn apples""" + pass + + def custom_action(self, agent, action): + """Execute any custom, non-move actions that may be defined, like fire or clean""" + pass + + def custom_map_update(self): + """Custom map updates that don't have to do with agent actions""" + pass + + def setup_agents(self): + """Construct all the agents for the environment""" + raise NotImplementedError +``` + diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..3326b384 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,42 @@ +FROM nvidia/cuda:9.0-cudnn7-runtime-ubuntu16.04 + +# Apt updates +RUN apt-get update \ + && apt-get install -y \ + build-essential \ + zlib1g-dev \ + git \ + libreadline-gplv2-dev \ + libncursesw5-dev \ + libssl-dev \ + libsqlite3-dev \ + tk-dev \ + libgdbm-dev \ + libc6-dev \ + libbz2-dev \ + tmux \ + wget \ + python3-tk + +# Install python 3.6 +RUN wget https://www.python.org/ftp/python/3.6.7/Python-3.6.7.tar.xz \ + && tar xvf Python-3.6.7.tar.xz \ + && cd Python-3.6.7 \ + && ./configure --with-zlib=/usr/include \ + && make \ + && make install \ + && ln -s /usr/local/bin/python3 /usr/local/bin/python \ + && ln -s /usr/local/bin/pip3 /usr/local/bin/pip + +# Install project-specific python libraries +RUN pip install tensorflow-gpu==1.12.0 gym matplotlib opencv-python lz4 psutil flake8 ray + +# Symlinking for making ray work +RUN rm -r /usr/local/lib/python3.6/site-packages/ray/rllib \ + && ln -s /ray/python/ray/rllib /usr/local/lib/python3.6/site-packages/ray/rllib \ + && rm -r /usr/local/lib/python3.6/site-packages/ray/tune \ + && ln -s /ray/python/ray/tune /usr/local/lib/python3.6/site-packages/ray/tune + + + +WORKDIR /project diff --git a/docker/build.sh b/docker/build.sh new file mode 100644 index 00000000..be8373f7 --- /dev/null +++ b/docker/build.sh @@ -0,0 +1 @@ +sudo docker build . -t multi-agent-empathy diff --git a/docker/readme.md b/docker/readme.md new file mode 100644 index 00000000..e2179153 --- /dev/null +++ b/docker/readme.md @@ -0,0 +1,34 @@ +# Docker environment + +## Prerequisites + - docker-ce + - nvidia-docker + +## Run from local build + +### Building +``` +sudo sh ./build.sh +``` + +Optional - push to docker hub +``` +sudo docker tag multi-agent-empathy natashajaques/multi-agent-empathy +sudo docker push natashajaques/multi-agent-empathy +``` + +### Run + +``` +SEQ_SOC_PATH=/home/natasha/Developer/sequential_social_dilemma_games +RAY_PATH=/home/natasha/Developer/ray +RAY_RESULTS_PATH=/home/natasha/ray_results +sudo docker run --runtime=nvidia -v $SEQ_SOC_PATH:/project -v $RAY_PATH:/ray --rm multi-agent-empathy /bin/bash -c "python setup.py develop && python run_scripts/train_baseline_dqn_actions.py --use_gpu_for_driver --num_gpus=1" +``` + +## Run from Docker Hub +``` +SEQ_SOC_PATH=/home/natasha/Developer/sequential_social_dilemma_games +RAY_PATH=/home/natasha/Developer/ray +sudo docker run --runtime=nvidia -v $SEQ_SOC_PATH:/project -v $RAY_PATH:/ray --rm natashajaques/multi-agent-empathy /bin/bash -c "python setup.py develop && python run_scripts/train_baseline_dqn_actions.py --use_gpu_for_driver --num_gpus=1" +``` diff --git a/environment.yml b/environment.yml new file mode 100644 index 00000000..d58b7757 --- /dev/null +++ b/environment.yml @@ -0,0 +1,16 @@ +name: causal + +dependencies: + - python==3.6.8 + - pip: + - numpy==1.16.0 + - gym==0.10.9 + - matplotlib==3.0.2 + - opencv-python + - ray==0.6.4 + - tensorflow==1.12.0 + - scipy==1.2.0 + - setproctitle + - psutil + - lz4 + - boto3 diff --git a/images/cleanup.png b/images/cleanup.png new file mode 100644 index 0000000000000000000000000000000000000000..f65fc38e6390f16706d64dcc1d0b81504aade8c4 GIT binary patch literal 22030 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

SeF(-P)k1n^kT>|^b@ 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

%Sy&aa@TY~e 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 u4m;Slnoe?boq)TbmCm*I#?9F$M6wrpBE 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 literal 0 HcmV?d00001 diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/models/conv_to_fc_net.py b/models/conv_to_fc_net.py new file mode 100644 index 00000000..3d8499ed --- /dev/null +++ b/models/conv_to_fc_net.py @@ -0,0 +1,47 @@ +# Model taken from https://arxiv.org/pdf/1810.08647.pdf, +# INTRINSIC SOCIAL MOTIVATION VIA CAUSAL +# INFLUENCE IN MULTI-AGENT RL + + +# model is a single convolutional layer with a kernel of size 3, stride of size 1, and 6 output +# channels. This is connected to two fully connected layers of size 32 each + +import tensorflow as tf + +from ray.rllib.models.misc import normc_initializer, flatten +from ray.rllib.models.model import Model +import tensorflow.contrib.slim as slim + + +class ConvToFCNet(Model): + def _build_layers_v2(self, input_dict, num_outputs, options): + + inputs = input_dict["obs"] + + hiddens = [32, 32] + with tf.name_scope("custom_net"): + inputs = slim.conv2d( + inputs, + 6, + [3, 3], + 1, + activation_fn=tf.nn.relu, + scope="conv") + last_layer = flatten(inputs) + i = 1 + for size in hiddens: + label = "fc{}".format(i) + last_layer = slim.fully_connected( + last_layer, + size, + weights_initializer=normc_initializer(1.0), + activation_fn=tf.nn.relu, + scope=label) + i += 1 + output = slim.fully_connected( + last_layer, + num_outputs, + weights_initializer=normc_initializer(0.01), + activation_fn=None, + scope="fc_out") + return output, last_layer diff --git a/models/conv_to_fc_net_actions.py b/models/conv_to_fc_net_actions.py new file mode 100644 index 00000000..e6c2fc30 --- /dev/null +++ b/models/conv_to_fc_net_actions.py @@ -0,0 +1,60 @@ +# Model taken from https://arxiv.org/pdf/1810.08647.pdf, +# INTRINSIC SOCIAL MOTIVATION VIA CAUSAL +# INFLUENCE IN MULTI-AGENT RL + + +# model is a single convolutional layer with a kernel of size 3, stride of size 1, and 6 output +# channels. This is connected to two fully connected layers of size 32 each + +import tensorflow as tf + +from ray.rllib.models.misc import normc_initializer, flatten +from ray.rllib.models.model import Model +import tensorflow.contrib.slim as slim + + +class ConvToFCNetActions(Model): + def _build_layers_v2(self, input_dict, num_outputs, options): + # Extract other agents' actions + actions_batch = input_dict["others_actions"] + num_other_agents = options["custom_options"]["num_other_agents"] + one_hot_actions = tf.one_hot(actions_batch, num_outputs) + others_actions = tf.reshape(one_hot_actions, [-1, num_outputs * num_other_agents]) + others_actions = tf.cast(others_actions, tf.float32) + + inputs = input_dict["obs"] + + hiddens = [32, 32] + with tf.name_scope("custom_net"): + inputs = slim.conv2d( + inputs, + 6, + [3, 3], + 1, + activation_fn=tf.nn.relu, + scope="conv") + last_layer = flatten(inputs) + i = 1 + for size in hiddens: + label = "fc{}".format(i) + last_layer = slim.fully_connected( + last_layer, + size, + weights_initializer=normc_initializer(1.0), + activation_fn=tf.nn.relu, + scope=label) + i += 1 + + # Add the others_actions in as input directly to the LSTM + last_layer = tf.concat([last_layer, others_actions], 1) + + # Add an output layer just in case LSTM not used + output = slim.fully_connected( + last_layer, + num_outputs, + weights_initializer=normc_initializer(.01), + activation_fn=None, + scope="output_layer", + ) + + return output, last_layer diff --git a/models/conv_to_fc_net_actions_no_lstm.py b/models/conv_to_fc_net_actions_no_lstm.py new file mode 100644 index 00000000..72f1857c --- /dev/null +++ b/models/conv_to_fc_net_actions_no_lstm.py @@ -0,0 +1,60 @@ +# Model taken from https://arxiv.org/pdf/1810.08647.pdf, +# INTRINSIC SOCIAL MOTIVATION VIA CAUSAL +# INFLUENCE IN MULTI-AGENT RL + + +# model is a single convolutional layer with a kernel of size 3, stride of size 1, and 6 output +# channels. This is connected to two fully connected layers of size 32 each + +import tensorflow as tf + +from ray.rllib.models.misc import normc_initializer, flatten +from ray.rllib.models.model import Model +import tensorflow.contrib.slim as slim + + +class ConvToFCNetActions(Model): + def _build_layers_v2(self, input_dict, num_outputs, options): + # Extract other agents' actions + actions_batch = input_dict["other_actions"] + num_other_agents = options["custom_options"]["num_other_agents"] + one_hot_actions = tf.one_hot(actions_batch, num_outputs) + others_actions = tf.reshape(one_hot_actions, [-1, num_outputs * num_other_agents]) + others_actions = tf.cast(others_actions, tf.float32) + + inputs = input_dict["obs"] + + hiddens = [128, 128] + with tf.name_scope("custom_net"): + inputs = slim.conv2d( + inputs, + 32, + [3, 3], + 1, + activation_fn=tf.nn.relu, + scope="conv") + last_layer = flatten(inputs) + i = 1 + for size in hiddens: + label = "fc{}".format(i) + last_layer = slim.fully_connected( + last_layer, + size, + weights_initializer=normc_initializer(1.0), + activation_fn=tf.nn.relu, + scope=label) + i += 1 + + # Add the others_actions in as input directly to the LSTM + last_layer = tf.concat([last_layer, others_actions], 1) + + # Add an output layer just in case LSTM not used + output = slim.fully_connected( + last_layer, + num_outputs, + weights_initializer=normc_initializer(.01), + activation_fn=None, + scope="output_layer", + ) + + return output, last_layer diff --git a/models/conv_to_fc_net_no_lstm.py b/models/conv_to_fc_net_no_lstm.py new file mode 100644 index 00000000..fe5d26b4 --- /dev/null +++ b/models/conv_to_fc_net_no_lstm.py @@ -0,0 +1,47 @@ +# Model taken from https://arxiv.org/pdf/1810.08647.pdf, +# INTRINSIC SOCIAL MOTIVATION VIA CAUSAL +# INFLUENCE IN MULTI-AGENT RL + + +# model is a single convolutional layer with a kernel of size 3, stride of size 1, and 6 output +# channels. This is connected to two fully connected layers of size 32 each + +import tensorflow as tf + +from ray.rllib.models.misc import normc_initializer, flatten +from ray.rllib.models.model import Model +import tensorflow.contrib.slim as slim + + +class ConvToFCNet(Model): + def _build_layers_v2(self, input_dict, num_outputs, options): + + inputs = input_dict["obs"] + + hiddens = [128, 128] + with tf.name_scope("custom_net"): + inputs = slim.conv2d( + inputs, + 32, + [3, 3], + 1, + activation_fn=tf.nn.relu, + scope="conv") + last_layer = flatten(inputs) + i = 1 + for size in hiddens: + label = "fc{}".format(i) + last_layer = slim.fully_connected( + last_layer, + size, + weights_initializer=normc_initializer(1.0), + activation_fn=tf.nn.relu, + scope=label) + i += 1 + output = slim.fully_connected( + last_layer, + num_outputs, + weights_initializer=normc_initializer(0.01), + activation_fn=None, + scope="fc_out") + return output, last_layer diff --git a/ray_autoscale.yaml b/ray_autoscale.yaml new file mode 100644 index 00000000..467943d5 --- /dev/null +++ b/ray_autoscale.yaml @@ -0,0 +1,102 @@ +# cluster.yaml ========================================= + +# An unique identifier for the head node and workers of this cluster. +cluster_name: eugene_test # + +# The minimum number of workers nodes to launch in addition to the head +# node. This number should be >= 0. + +min_workers: 5 # + +# The maximum number of workers nodes to launch in addition to the head +# node. This takes precedence over min_workers. +max_workers: 5 + +initial_workers: 5 + +# The autoscaler will scale up the cluster to this target fraction of resource +# usage. For example, if a cluster of 10 nodes is 100% busy and +# target_utilization is 0.8, it would resize the cluster to 13. This fraction +# can be decreased to increase the aggressiveness of upscaling. +target_utilization_fraction: 0.8 + +# If a node is idle for this many minutes, it will be removed. +idle_timeout_minutes: 5 + +# Cloud-provider specific configuration. +provider: + type: aws + region: us-west-1 + availability_zone: us-west-1a + +# How Ray will authenticate with newly launched nodes. +auth: + ssh_user: ubuntu +# By default Ray creates a new private keypair, but you can also use your own. +# If you do so, make sure to also set "KeyName" in the head and worker node +# configurations below. +# ssh_private_key: /home/zian/Desktop/research/LQR/ethan.pem + +# Provider-specific config for the head node, e.g. instance type. By default +# Ray will auto-configure unspecified fields such as SubnetId and KeyName. +# For more documentation on available fields, see: +# http://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.ServiceResource.create_instances +head_node: + InstanceType: c4.8xlarge + ImageId: ami-0077cb35e9589eada # Flow AMI (Ubuntu) + InstanceMarketOptions: + MarketType: spot + #Additional options can be found in the boto docs, e.g. +# SpotOptions: +# MaxPrice: "1.0" + + # You can provision additional disk space with a conf as follows +# BlockDeviceMappings: +# - DeviceName: /dev/sda1 +# Ebs: +# VolumeSize: 50 + # Additional options in the boto docs. + +# Provider-specific config for worker nodes, e.g. instance type. By default +# Ray will auto-configure unspecified fields such as SubnetId and KeyName. +# For more documentation on available fields, see: +# http://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.ServiceResource.create_instances +worker_nodes: + InstanceType: c4.8xlarge + ImageId: ami-0077cb35e9589eada # Flow AMI (Ubuntu) + + #Run workers on spot by default. Comment this out to use on-demand. + InstanceMarketOptions: + MarketType: spot + # Additional options can be found in the boto docs, e.g. +# SpotOptions: +# MaxPrice: "1.0" + +file_mounts: { +# path to your repo and the desired branch name +#"/tmp/path": ".git/refs/heads/", +} + +setup_commands: + # checkout your desired branch on all worker nodes + - echo 'export PATH="/home/ubuntu/anaconda3/bin:$PATH"' >> ~/.bashrc + - cd sequential_social_dilemma_games && git fetch && git checkout visible_actions && git pull && pip install -r requirements_autoscale.txt + - cd sequential_social_dilemma_games && python setup.py develop + - cd ray/python/ray/rllib && git checkout causal_a3c && git pull && python setup-rllib-dev.py --yes + +# Custom commands that will be run on the head node after common setup. +head_setup_commands: + - PATH="/home/ubuntu/anaconda3/bin:$PATH" yes | ~/anaconda3/bin/conda install boto3=1.4.8 + +# Custom commands that will be run on worker nodes after common setup. +worker_setup_commands: + - conda install python=3.6.8 -y + +# Command to start ray on the head node. You don't need to change this. +head_start_ray_commands: + - ray stop + - ulimit -n 65536; ray start --head --redis-port=6379 --object-manager-port=8076 --autoscaling-config=~/ray_bootstrap_config.yaml +# Command to start ray on worker nodes. You don't need to change this. +worker_start_ray_commands: + - ray stop + - ulimit -n 65536; ray start --redis-address=$RAY_HEAD_IP:6379 --object-manager-port=8076 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..a3173312 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,49 @@ +absl-py==0.7.0 +astor==0.7.1 +atomicwrites==1.2.1 +attrs==18.2.0 +awscli==1.16.174 +certifi==2018.11.29 +chardet==3.0.4 +Click==7.0 +colorama==0.4.1 +cycler==0.10.0 +filelock==3.0.10 +flatbuffers==1.10 +funcsigs==1.0.2 +future==0.17.1 +gast==0.2.2 +grpcio==1.18.0 +gym==0.10.9 +h5py==2.9.0 +idna==2.8 +jupyter==1.0.0 +Keras-Applications==1.0.6 +Keras-Preprocessing==1.0.5 +kiwisolver==1.0.1 +lz4==2.1.6 +Markdown==3.0.1 +matplotlib==3.0.2 +more-itertools==5.0.0 +numpy==1.16.0 +opencv-python==4.0.0.21 +pluggy==0.8.1 +protobuf==3.6.1 +psutil==5.5.0 +py==1.7.0 +pyglet==1.3.2 +pyparsing==2.3.1 +pytest==4.1.1 +python-dateutil==2.7.5 +ray==0.6.4 +PyYAML==4.2b1 +redis==3.0.1 +requests==2.21.0 +scipy==1.2.0 +setproctitle==1.1.10 +six==1.12.0 +tensorboard==1.12.2 +tensorflow==1.12.2 +termcolor==1.1.0 +urllib3==1.24.1 +Werkzeug==0.14.1 diff --git a/requirements_autoscale.txt b/requirements_autoscale.txt new file mode 100644 index 00000000..2bd39eb1 --- /dev/null +++ b/requirements_autoscale.txt @@ -0,0 +1,11 @@ +numpy==1.14.0 +gym==0.10.5 +matplotlib==3.0.2 +opencv-python +ray==0.6.4 +tensorflow==1.12.0 +scipy==1.2.0 +setproctitle +psutil +lz4 +boto3 diff --git a/rollout.py b/rollout.py new file mode 100644 index 00000000..45d1be4d --- /dev/null +++ b/rollout.py @@ -0,0 +1,126 @@ +"""Defines a multi-agent controller to rollout environment episodes w/ + agent policies.""" + +import utility_funcs +import numpy as np +import os +import sys +import shutil +import tensorflow as tf + +from social_dilemmas.envs.cleanup import CleanupEnv +from social_dilemmas.envs.harvest import HarvestEnv + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string( + 'vid_path', os.path.abspath(os.path.join(os.path.dirname(__file__), './videos')), + 'Path to directory where videos are saved.') +tf.app.flags.DEFINE_string( + 'env', 'cleanup', + 'Name of the environment to rollout. Can be cleanup or harvest.') +tf.app.flags.DEFINE_string( + 'render_type', 'pretty', + 'Can be pretty or fast. Implications obvious.') +tf.app.flags.DEFINE_integer( + 'fps', 8, + 'Number of frames per second.') + + +class Controller(object): + + def __init__(self, env_name='cleanup'): + self.env_name = env_name + if env_name == 'harvest': + print('Initializing Harvest environment') + self.env = HarvestEnv(num_agents=5, render=True) + elif env_name == 'cleanup': + print('Initializing Cleanup environment') + self.env = CleanupEnv(num_agents=5, render=True) + else: + print('Error! Not a valid environment type') + return + + self.env.reset() + + # TODO: initialize agents here + + def rollout(self, horizon=50, save_path=None): + """ Rollout several timesteps of an episode of the environment. + + Args: + horizon: The number of timesteps to roll out. + save_path: If provided, will save each frame to disk at this + location. + """ + rewards = [] + observations = [] + shape = self.env.world_map.shape + full_obs = [np.zeros( + (shape[0], shape[1], 3), dtype=np.uint8) for i in range(horizon)] + + for i in range(horizon): + agents = list(self.env.agents.values()) + action_dim = agents[0].action_space.n + rand_action = np.random.randint(action_dim, size=5) + obs, rew, dones, info, = self.env.step({'agent-0': rand_action[0], + 'agent-1': rand_action[1], + 'agent-2': rand_action[2], + 'agent-3': rand_action[3], + 'agent-4': rand_action[4]}) + + sys.stdout.flush() + + if save_path is not None: + self.env.render(filename=save_path + 'frame' + str(i).zfill(6) + '.png') + + rgb_arr = self.env.map_to_colors() + full_obs[i] = rgb_arr.astype(np.uint8) + observations.append(obs['agent-0']) + rewards.append(rew['agent-0']) + + return rewards, observations, full_obs + + def render_rollout(self, horizon=50, path=None, + render_type='pretty', fps=8): + """ Render a rollout into a video. + + Args: + horizon: The number of timesteps to roll out. + path: Directory where the video will be saved. + render_type: Can be 'pretty' or 'fast'. Impliciations obvious. + fps: Integer frames per second. + """ + if path is None: + path = os.path.abspath(os.path.dirname(__file__)) + '/videos' + print(path) + if not os.path.exists(path): + os.makedirs(path) + video_name = self.env_name + '_trajectory' + + if render_type == 'pretty': + image_path = os.path.join(path, 'frames/') + if not os.path.exists(image_path): + os.makedirs(image_path) + + rewards, observations, full_obs = self.rollout( + horizon=horizon, save_path=image_path) + utility_funcs.make_video_from_image_dir(path, image_path, fps=fps, + video_name=video_name) + + # Clean up images + shutil.rmtree(image_path) + else: + rewards, observations, full_obs = self.rollout(horizon=horizon) + utility_funcs.make_video_from_rgb_imgs(full_obs, path, fps=fps, + video_name=video_name) + + +def main(unused_argv): + c = Controller(env_name=FLAGS.env) + c.render_rollout(path=FLAGS.vid_path, render_type=FLAGS.render_type, + fps=FLAGS.fps) + + +if __name__ == '__main__': + tf.app.run(main) diff --git a/run_scripts/README.md b/run_scripts/README.md new file mode 100644 index 00000000..41343ff1 --- /dev/null +++ b/run_scripts/README.md @@ -0,0 +1,24 @@ +To run scripts on AWS do the following: + +1. Push up the branch with the scripts you want to run. If no changes need to be made to the branch, you're good. + +2. Go to ray_autoscale.yaml and change the following + - In the line + ```cd sequential_social_dilemma_games && git checkout visible_actions && git pull && pip install -r requirements_autoscale.txt``` + put in the branch you want to check out + - In the line + ```cd ray/python/ray/rllib && git checkout causal_a3c && python setup-rllib-dev.py --yes``` + put the Ray branch you want to check out + - Set min_workers, max_workers, initial_workers equal to the desired number of total instances. If you are running a grid search + over two elements, each which two values, set it to 3 (3 workers + 1 head = 4 experiments). Be warned that if you set it to more + than 20 there will be an error. + +3. If you're submitting a script that can be run without any command line args, run: + ```ray submit ray_autoscale.yaml --start --stop --cluster-name --tmux``` + The --tmux flag is used to not have it print output to your screen. If you leave it off, you'll get output to your screen + but the experiment will stop if you close your laptop. + +3. If you're submitting a script that should be run with command line args run + ```ray exec ray_autoscale.yaml "command to exec" --start --stop --cluster-name --tmux``` + As an example, to exec train_baseline.py you would run + ```ray submit ray_autoscale.yaml "python sequential_social_dilemma_games/run_scripts/train_baseline.py arg1 arg2" --start --stop --cluster-name --tmux```` diff --git a/run_scripts/train_a3c_equity.py b/run_scripts/train_a3c_equity.py new file mode 100644 index 00000000..eb83398d --- /dev/null +++ b/run_scripts/train_a3c_equity.py @@ -0,0 +1,229 @@ +import ray +from ray import tune +from ray.rllib.agents.registry import get_agent_class +from ray.rllib.agents.a3c.a3c_policy_graph_inequity2 import A3CPolicyGraph +from ray.rllib.models import ModelCatalog +from ray.tune import run_experiments +from ray.tune.registry import register_env +import tensorflow as tf + +from social_dilemmas.envs.harvest import HarvestEnv +from social_dilemmas.envs.cleanup import CleanupEnv +from models.conv_to_fc_net_actions import ConvToFCNetActions + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string( + 'exp_name', 'equity_harvest', + 'Name of the ray_results experiment directory where results are stored.') +tf.app.flags.DEFINE_string( + 'env', 'harvest', + 'Name of the environment to rollout. Can be cleanup or harvest.') +tf.app.flags.DEFINE_integer( + 'num_agents', 5, + 'Number of agent policies') +tf.app.flags.DEFINE_integer( + 'num_cpus', 6, + 'Number of available CPUs') +tf.app.flags.DEFINE_integer( + 'num_gpus', 0, + 'Number of available GPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpus_for_workers', False, + 'Set to true to run workers on GPUs rather than CPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpu_for_driver', False, + 'Set to true to run driver on GPU rather than CPU.') +tf.app.flags.DEFINE_float( + 'num_workers_per_device', 1, + 'Number of workers to place on a single device (CPU or GPU)') +tf.app.flags.DEFINE_boolean( + 'tune', True, + 'Set to true to do hyperparameter tuning.') +tf.app.flags.DEFINE_boolean( + 'debug', False, + 'Set to true to run in a debugging / testing mode with less memory.') +#tf.app.flags.DEFINE_boolean( + #'resume', False, + #'Set to true to resume a previously stopped experiment.') + +harvest_default_params = { + 'lr_init': 0.00136, + 'lr_final': 0.000028, + 'entropy_coeff': .000687} + +cleanup_default_params = { + 'lr_init': 0.00126, + 'lr_final': 0.000012, + 'entropy_coeff': .00176} + + +def setup(env, hparams, num_cpus, num_gpus, num_agents, use_gpus_for_workers=False, + use_gpu_for_driver=False, num_workers_per_device=1, tune_hparams=False): + if env == 'harvest': + def env_creator(_): + return HarvestEnv(num_agents=num_agents) + + single_env = HarvestEnv() + default_hparams = harvest_default_params + else: + def env_creator(_): + return CleanupEnv(num_agents=num_agents) + + single_env = CleanupEnv() + default_hparams = cleanup_default_params + + env_name = env + "_env" + register_env(env_name, env_creator) + + obs_space = single_env.observation_space + act_space = single_env.action_space + + # Each policy can have a different configuration (including custom model) + def gen_policy(agent_id): + return (A3CPolicyGraph, obs_space, act_space, + {'num_other_agents': num_agents - 1, 'agent_id': agent_id}) + + # Setup A3C with an ensemble of `num_policies` different policy graphs + policy_graphs = {} + for i in range(num_agents): + agent_id = 'agent-' + str(i) + policy_graphs[agent_id] = gen_policy(agent_id) + + def policy_mapping_fn(agent_id): + return agent_id + + # register the custom model + model_name = "conv_to_fc_net_actions" + ModelCatalog.register_custom_model(model_name, ConvToFCNetActions) + + algorithm = 'A3C' + agent_cls = get_agent_class(algorithm) + config = agent_cls._default_config.copy() + + # information for replay + config['env_config']['func_create'] = tune.function(env_creator) + config['env_config']['env_name'] = env_name + config['env_config']['run'] = algorithm + + # Calculate device configurations + gpus_for_driver = int(use_gpu_for_driver) + cpus_for_driver = 1 - gpus_for_driver + if use_gpus_for_workers: + spare_gpus = (num_gpus - gpus_for_driver) + num_workers = int(spare_gpus * num_workers_per_device) + num_gpus_per_worker = spare_gpus / num_workers + num_cpus_per_worker = 0 + else: + spare_cpus = (num_cpus - cpus_for_driver) + num_workers = int(spare_cpus * num_workers_per_device) + num_gpus_per_worker = 0 + num_cpus_per_worker = spare_cpus / num_workers + + # hyperparams + if tune_hparams: + config.update({ + "train_batch_size": 2000, + "horizon": 1000, + # "lr_schedule": [[0, tune.grid_search([5e-4, 5e-3])], + # [20000000, tune.grid_search([5e-4, 5e-5, 5e-6])]], + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + "entropy_coeff": tune.grid_search([0, -1e-1]), + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net_actions", "use_lstm": True, + "lstm_cell_size": 128, "lstm_use_prev_action_reward": True, + "custom_options": { + "num_other_agents": num_agents, + "moa_weight": tune.grid_search([10.0]), + "train_moa_only_when_visible": tune.grid_search([True, False]), + "influence_reward_clip": 10, + "influence_divergence_measure": 'kl', + "influence_reward_weight": tune.grid_search([1.0]), + "influence_curriculum_steps": tune.grid_search([10e6]), + "influence_scaledown_start": tune.grid_search([100e6]), + "influence_scaledown_end": tune.grid_search([300e6]), + "influence_scaledown_final_val": tune.grid_search([.5]), + "influence_only_when_visible": tune.grid_search([True, False])}} + + }) + else: + config.update({ + # "train_batch_size": 2000, + "horizon": 1000, + # "lr_schedule": [[0, default_hparams['lr_init']], + # [20000000, default_hparams['lr_final']]], + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + # "entropy_coeff": default_hparams['entropy_coeff'], + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net_actions", "use_lstm": True, + "lstm_cell_size": 128, "lstm_use_prev_action_reward": True, + "custom_options": {"num_other_agents": num_agents, + "train_moa_only_when_visible": True, + "moa_weight": 12.0, + "influence_reward_clip": 10, + "influence_divergence_measure": 'kl', + "influence_reward_weight": 2.5, + "influence_curriculum_steps": 10e6, + "influence_scaledown_start": 100e6, + "influence_scaledown_end": 300e6, + "influence_scaledown_final_val": 0.5, + "influence_only_when_visible": True}} + + }) + return algorithm, env_name, config + + +def main(unused_argv): + # if FLAGS.debug: + # ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(25e10), + # redis_max_memory=int(50e10)) + # else: + # ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(25e10), + # redis_max_memory=int(50e10)) + ray.init() # redis_address="localhost:6379" + if FLAGS.env == 'harvest': + hparams = harvest_default_params + else: + hparams = cleanup_default_params + alg_run, env_name, config = setup(FLAGS.env, hparams, FLAGS.num_cpus, + FLAGS.num_gpus, FLAGS.num_agents, + FLAGS.use_gpus_for_workers, + FLAGS.use_gpu_for_driver, + FLAGS.num_workers_per_device, FLAGS.tune) + + if FLAGS.exp_name is None: + exp_name = FLAGS.env + '_A3C_influence' + else: + exp_name = FLAGS.exp_name + print('Commencing experiment', exp_name) + + run_experiments({ + exp_name: { + "run": alg_run, + "env": env_name, + "stop": { + "training_iteration": 20000 + }, + 'checkpoint_freq': 100, + "config": config, + #'upload_dir': 's3://njaques.experiments/fourth_reproduction/a3c_causal_influence_cleanup' + } + })#, resume=FLAGS.resume) + + +if __name__ == '__main__': + tf.app.run(main) diff --git a/run_scripts/train_a3c_influence.py b/run_scripts/train_a3c_influence.py new file mode 100644 index 00000000..8c653a50 --- /dev/null +++ b/run_scripts/train_a3c_influence.py @@ -0,0 +1,230 @@ +import ray +from ray import tune +from ray.rllib.agents.registry import get_agent_class +from ray.rllib.agents.a3c.a3c_policy_graph_causal import A3CPolicyGraph +from ray.rllib.models import ModelCatalog +from ray.tune import run_experiments +from ray.tune.registry import register_env +import tensorflow as tf + +from social_dilemmas.envs.harvest import HarvestEnv +from social_dilemmas.envs.cleanup import CleanupEnv +from models.conv_to_fc_net_actions import ConvToFCNetActions + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string( + 'exp_name', 'causal_influence_cleanup', + 'Name of the ray_results experiment directory where results are stored.') +tf.app.flags.DEFINE_string( + 'env', 'cleanup', + 'Name of the environment to rollout. Can be cleanup or harvest.') +tf.app.flags.DEFINE_integer( + 'num_agents', 5, + 'Number of agent policies') +tf.app.flags.DEFINE_integer( + 'num_cpus', 17, + 'Number of available CPUs') +tf.app.flags.DEFINE_integer( + 'num_gpus', 0, + 'Number of available GPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpus_for_workers', False, + 'Set to true to run workers on GPUs rather than CPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpu_for_driver', False, + 'Set to true to run driver on GPU rather than CPU.') +tf.app.flags.DEFINE_float( + 'num_workers_per_device', 1, + 'Number of workers to place on a single device (CPU or GPU)') +tf.app.flags.DEFINE_boolean( + 'tune', True, + 'Set to true to do hyperparameter tuning.') +tf.app.flags.DEFINE_boolean( + 'debug', False, + 'Set to true to run in a debugging / testing mode with less memory.') +#tf.app.flags.DEFINE_boolean( + #'resume', False, + #'Set to true to resume a previously stopped experiment.') + +harvest_default_params = { + 'lr_init': 0.00136, + 'lr_final': 0.000028, + 'entropy_coeff': .000687} + +cleanup_default_params = { + 'lr_init': 0.00126, + 'lr_final': 0.000012, + 'entropy_coeff': .00176} + + +def setup(env, hparams, num_cpus, num_gpus, num_agents, use_gpus_for_workers=False, + use_gpu_for_driver=False, num_workers_per_device=1, tune_hparams=False): + if env == 'harvest': + def env_creator(_): + return HarvestEnv(num_agents=num_agents) + + single_env = HarvestEnv() + default_hparams = harvest_default_params + else: + def env_creator(_): + return CleanupEnv(num_agents=num_agents) + + single_env = CleanupEnv() + default_hparams = cleanup_default_params + + env_name = env + "_env" + register_env(env_name, env_creator) + + obs_space = single_env.observation_space + act_space = single_env.action_space + + # Each policy can have a different configuration (including custom model) + def gen_policy(agent_id): + return (A3CPolicyGraph, obs_space, act_space, + {'num_other_agents': num_agents - 1, 'agent_id': agent_id}) + + # Setup A3C with an ensemble of `num_policies` different policy graphs + policy_graphs = {} + for i in range(num_agents): + agent_id = 'agent-' + str(i) + policy_graphs[agent_id] = gen_policy(agent_id) + + def policy_mapping_fn(agent_id): + return agent_id + + # register the custom model + model_name = "conv_to_fc_net_actions" + ModelCatalog.register_custom_model(model_name, ConvToFCNetActions) + + algorithm = 'A3C' + agent_cls = get_agent_class(algorithm) + config = agent_cls._default_config.copy() + + # information for replay + config['env_config']['func_create'] = tune.function(env_creator) + config['env_config']['env_name'] = env_name + config['env_config']['run'] = algorithm + + # Calculate device configurations + gpus_for_driver = int(use_gpu_for_driver) + cpus_for_driver = 1 - gpus_for_driver + if use_gpus_for_workers: + spare_gpus = (num_gpus - gpus_for_driver) + num_workers = int(spare_gpus * num_workers_per_device) + num_gpus_per_worker = spare_gpus / num_workers + num_cpus_per_worker = 0 + else: + spare_cpus = (num_cpus - cpus_for_driver) + num_workers = int(spare_cpus * num_workers_per_device) + num_gpus_per_worker = 0 + num_cpus_per_worker = spare_cpus / num_workers + + # hyperparams + if tune_hparams: + config.update({ + "train_batch_size": 2000, + "horizon": 1000, + # "lr_schedule": [[0, tune.grid_search([5e-4, 5e-3])], + # [20000000, tune.grid_search([5e-4, 5e-5, 5e-6])]], + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + "entropy_coeff": tune.grid_search([0, -1e-1]), + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net_actions", "use_lstm": True, + "lstm_cell_size": 128, "lstm_use_prev_action_reward": True, + "custom_options": { + "num_other_agents": num_agents, + "moa_weight": tune.grid_search([10.0]), + "train_moa_only_when_visible": tune.grid_search([True, False]), + "influence_reward_clip": 10, + "influence_divergence_measure": 'kl', + "influence_reward_weight": tune.grid_search([1.0]), + "influence_curriculum_steps": tune.grid_search([10e6]), + "influence_scaledown_start": tune.grid_search([100e6]), + "influence_scaledown_end": tune.grid_search([300e6]), + "influence_scaledown_final_val": tune.grid_search([.5]), + "influence_only_when_visible": tune.grid_search([True, False])}} + + }) + else: + config.update({ + # "train_batch_size": 2000, + "horizon": 1000, + # "lr_schedule": [[0, default_hparams['lr_init']], + # [20000000, default_hparams['lr_final']]], + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + # "entropy_coeff": default_hparams['entropy_coeff'], + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net_actions", "use_lstm": True, + "lstm_cell_size": 128, "lstm_use_prev_action_reward": True, + "custom_options": {"num_other_agents": num_agents, + "train_moa_only_when_visible": True, + "moa_weight": 12.0, + "influence_reward_clip": 10, + "influence_divergence_measure": 'kl', + "influence_reward_weight": 2.5, + "influence_curriculum_steps": 10e6, + "influence_scaledown_start": 100e6, + "influence_scaledown_end": 300e6, + "influence_scaledown_final_val": 0.5, + "influence_only_when_visible": True}} + + }) + return algorithm, env_name, config + + +def main(unused_argv): + # if FLAGS.debug: + # ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(25e10), + # redis_max_memory=int(50e10)) + # else: + # ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(25e10), + # redis_max_memory=int(50e10)) + ray.init() # redis_address="localhost:6379" + if FLAGS.env == 'harvest': + hparams = harvest_default_params + else: + hparams = cleanup_default_params + alg_run, env_name, config = setup(FLAGS.env, hparams, FLAGS.num_cpus, + FLAGS.num_gpus, FLAGS.num_agents, + FLAGS.use_gpus_for_workers, + FLAGS.use_gpu_for_driver, + FLAGS.num_workers_per_device, FLAGS.tune) + + if FLAGS.exp_name is None: + exp_name = FLAGS.env + '_A3C_influence' + else: + exp_name = FLAGS.exp_name + print('Commencing experiment', exp_name) + + run_experiments({ + exp_name: { + "run": alg_run, + "env": env_name, + "stop": { + "training_iteration": 1000 + }, + 'checkpoint_freq': 10, + "config": config, + 'upload_dir': 's3://njaques.experiments/fourth_reproduction/a3c_causal_influence_cleanup' + + } + })#, resume=FLAGS.resume) + + +if __name__ == '__main__': + tf.app.run(main) diff --git a/run_scripts/train_a3c_moa.py b/run_scripts/train_a3c_moa.py new file mode 100644 index 00000000..b866499a --- /dev/null +++ b/run_scripts/train_a3c_moa.py @@ -0,0 +1,213 @@ +import ray +from ray import tune +from ray.rllib.agents.registry import get_agent_class +from ray.rllib.agents.a3c.a3c_policy_graph_moa import A3CPolicyGraph +from ray.rllib.models import ModelCatalog +from ray.tune import run_experiments +from ray.tune.registry import register_env +import tensorflow as tf + +from social_dilemmas.envs.harvest import HarvestEnv +from social_dilemmas.envs.cleanup import CleanupEnv +from models.conv_to_fc_net_actions import ConvToFCNetActions + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string( + 'exp_name', 'causal_moa_cleanup', + 'Name of the ray_results experiment directory where results are stored.') +tf.app.flags.DEFINE_string( + 'env', 'cleanup', + 'Name of the environment to rollout. Can be cleanup or harvest.') +tf.app.flags.DEFINE_integer( + 'num_agents', 5, + 'Number of agent policies') +tf.app.flags.DEFINE_integer( + 'num_cpus', 17, + 'Number of available CPUs') +tf.app.flags.DEFINE_integer( + 'num_gpus', 0, + 'Number of available GPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpus_for_workers', False, + 'Set to true to run workers on GPUs rather than CPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpu_for_driver', False, + 'Set to true to run driver on GPU rather than CPU.') +tf.app.flags.DEFINE_float( + 'num_workers_per_device', 1, + 'Number of workers to place on a single device (CPU or GPU)') +tf.app.flags.DEFINE_boolean( + 'tune', True, + 'Set to true to do hyperparameter tuning.') +tf.app.flags.DEFINE_boolean( + 'debug', False, + 'Set to true to run in a debugging / testing mode with less memory.') +tf.app.flags.DEFINE_boolean( + 'resume', False, + 'Set to true to resume a previously stopped experiment.') + +harvest_default_params = { + 'lr_init': 0.00136, + 'lr_final': 0.000028, + 'entropy_coeff': .000687} + +cleanup_default_params = { + 'lr_init': 0.00126, + 'lr_final': 0.000012, + 'entropy_coeff': .00176} + + +def setup(env, hparams, num_cpus, num_gpus, num_agents, use_gpus_for_workers=False, + use_gpu_for_driver=False, num_workers_per_device=1, tune_hparams=False): + if env == 'harvest': + def env_creator(_): + return HarvestEnv(num_agents=num_agents) + + single_env = HarvestEnv() + default_hparams = harvest_default_params + else: + def env_creator(_): + return CleanupEnv(num_agents=num_agents) + + single_env = CleanupEnv() + default_hparams = cleanup_default_params + + env_name = env + "_env" + register_env(env_name, env_creator) + + obs_space = single_env.observation_space + act_space = single_env.action_space + + # Each policy can have a different configuration (including custom model) + def gen_policy(agent_id): + return (A3CPolicyGraph, obs_space, act_space, + {'num_other_agents': num_agents - 1, 'agent_id': agent_id}) + + # Setup A3C with an ensemble of `num_policies` different policy graphs + policy_graphs = {} + for i in range(num_agents): + agent_id = 'agent-' + str(i) + policy_graphs[agent_id] = gen_policy(agent_id) + + def policy_mapping_fn(agent_id): + return agent_id + + # register the custom model + model_name = "conv_to_fc_net_actions" + ModelCatalog.register_custom_model(model_name, ConvToFCNetActions) + + algorithm = 'A3C' + agent_cls = get_agent_class(algorithm) + config = agent_cls._default_config.copy() + + # information for replay + config['env_config']['func_create'] = tune.function(env_creator) + config['env_config']['env_name'] = env_name + config['env_config']['run'] = algorithm + + # Calculate device configurations + gpus_for_driver = int(use_gpu_for_driver) + cpus_for_driver = 1 - gpus_for_driver + if use_gpus_for_workers: + spare_gpus = (num_gpus - gpus_for_driver) + num_workers = int(spare_gpus * num_workers_per_device) + num_gpus_per_worker = spare_gpus / num_workers + num_cpus_per_worker = 0 + else: + spare_cpus = (num_cpus - cpus_for_driver) + num_workers = int(spare_cpus * num_workers_per_device) + num_gpus_per_worker = 0 + num_cpus_per_worker = spare_cpus / num_workers + + # hyperparams + if tune_hparams: + config.update({ + "train_batch_size": 128, + "horizon": 1000, + "lr_schedule": [[0, tune.grid_search([5e-4, 5e-3])], + [20000000, tune.grid_search([5e-4, 5e-5, 5e-6])]], + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + "entropy_coeff": tune.grid_search([0, -1e-1, -1e-2]), + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net_actions", "use_lstm": True, + "lstm_cell_size": 128, "lstm_use_prev_action_reward": True, + "custom_options": { + "num_other_agents": num_agents, + "moa_weight": tune.grid_search([5.0, 10.0, 20.0]), + "train_moa_only_when_visible": tune.grid_search([True, False])}} + + }) + else: + config.update({ + # "train_batch_size": 128, + "horizon": 1000, + # "lr_schedule": [[0, default_hparams['lr_init']], + # [20000000, default_hparams['lr_final']]], + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + # "entropy_coeff": default_hparams['entropy_coeff'], + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net_actions", "use_lstm": True, + "lstm_cell_size": 128, "lstm_use_prev_action_reward": True, + "custom_options": {"num_other_agents": num_agents, + "moa_weight": 12.0, + "train_moa_only_when_visible": True}} + + }) + return algorithm, env_name, config + + +def main(unused_argv): + # if FLAGS.debug: + # ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(25e10), + # redis_max_memory=int(50e10)) + # else: + # ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(25e10), + # redis_max_memory=int(50e10)) + ray.init(redis_address="localhost:6379") + if FLAGS.env == 'harvest': + hparams = harvest_default_params + else: + hparams = cleanup_default_params + alg_run, env_name, config = setup(FLAGS.env, hparams, FLAGS.num_cpus, + FLAGS.num_gpus, FLAGS.num_agents, + FLAGS.use_gpus_for_workers, + FLAGS.use_gpu_for_driver, + FLAGS.num_workers_per_device, FLAGS.tune) + + if FLAGS.exp_name is None: + exp_name = FLAGS.env + '_A3C_moa' + else: + exp_name = FLAGS.exp_name + print('Commencing experiment', exp_name) + + run_experiments({ + exp_name: { + "run": alg_run, + "env": env_name, + "stop": { + "training_iteration": 20000 + }, + 'checkpoint_freq': 100, + "config": config, + 'upload_dir': 's3://njaques.experiments/fourth_reproduction/a3c_causal_moa_cleanup' + } + }, resume=FLAGS.resume) + + +if __name__ == '__main__': + tf.app.run(main) diff --git a/run_scripts/train_baseline.py b/run_scripts/train_baseline.py new file mode 100644 index 00000000..80d3cfaa --- /dev/null +++ b/run_scripts/train_baseline.py @@ -0,0 +1,183 @@ +import ray +from ray import tune +from ray.rllib.agents.registry import get_agent_class +from ray.rllib.agents.a3c.a3c_tf_policy_graph import A3CPolicyGraph +from ray.rllib.models import ModelCatalog +from ray.tune import run_experiments +from ray.tune.registry import register_env +import tensorflow as tf + +from social_dilemmas.envs.harvest import HarvestEnv +from social_dilemmas.envs.cleanup import CleanupEnv +from models.conv_to_fc_net import ConvToFCNet + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string( + 'exp_name', 'a3c_baseline', + 'Name of the ray_results experiment directory where results are stored.') +tf.app.flags.DEFINE_string( + 'env', 'harvest', + 'Name of the environment to rollout. Can be cleanup or harvest.') +tf.app.flags.DEFINE_string( + 'algorithm', 'A3C', + 'Name of the rllib algorithm to use.') +tf.app.flags.DEFINE_integer( + 'num_agents', 5, + 'Number of agent policies') +tf.app.flags.DEFINE_integer( + 'train_batch_size', 2000, + 'Size of the total dataset over which one epoch is computed.') +tf.app.flags.DEFINE_integer( + 'checkpoint_frequency', 100, + 'Number of steps before a checkpoint is saved.') +tf.app.flags.DEFINE_integer( + 'training_iterations', 10000, + 'Total number of steps to train for') +tf.app.flags.DEFINE_integer( + 'num_cpus', 38, + 'Number of available CPUs') +tf.app.flags.DEFINE_integer( + 'num_gpus', 0, + 'Number of available GPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpus_for_workers', False, + 'Set to true to run workers on GPUs rather than CPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpu_for_driver', False, + 'Set to true to run driver on GPU rather than CPU.') +tf.app.flags.DEFINE_float( + 'num_workers_per_device', 1, + 'Number of workers to place on a single device (CPU or GPU)') + +harvest_default_params = { + 'lr_init': 0.00136, + 'lr_final': 0.000028, + 'entropy_coeff': -.000687} + +cleanup_default_params = { + 'lr_init': 0.00126, + 'lr_final': 0.000012, + 'entropy_coeff': -.00176} + + +def setup(env, hparams, algorithm, train_batch_size, num_cpus, num_gpus, + num_agents, use_gpus_for_workers=False, use_gpu_for_driver=False, + num_workers_per_device=1): + if env == 'harvest': + def env_creator(_): + return HarvestEnv(num_agents=num_agents) + + single_env = HarvestEnv() + else: + def env_creator(_): + return CleanupEnv(num_agents=num_agents) + + single_env = CleanupEnv() + + env_name = env + "_env" + register_env(env_name, env_creator) + + obs_space = single_env.observation_space + act_space = single_env.action_space + + # Each policy can have a different configuration (including custom model) + def gen_policy(): + return (A3CPolicyGraph, obs_space, act_space, {}) + + # Setup A3C with an ensemble of `num_policies` different policy graphs + policy_graphs = {} + for i in range(num_agents): + policy_graphs['agent-' + str(i)] = gen_policy() + + def policy_mapping_fn(agent_id): + return agent_id + + # register the custom model + model_name = "conv_to_fc_net" + ModelCatalog.register_custom_model(model_name, ConvToFCNet) + + agent_cls = get_agent_class(algorithm) + config = agent_cls._default_config.copy() + + # information for replay + config['env_config']['func_create'] = tune.function(env_creator) + config['env_config']['env_name'] = env_name + config['env_config']['run'] = algorithm + + # Calculate device configurations + gpus_for_driver = int(use_gpu_for_driver) + cpus_for_driver = 1 - gpus_for_driver + if use_gpus_for_workers: + spare_gpus = (num_gpus - gpus_for_driver) + num_workers = int(spare_gpus * num_workers_per_device) + num_gpus_per_worker = spare_gpus / num_workers + num_cpus_per_worker = 0 + else: + spare_cpus = (num_cpus - cpus_for_driver) + num_workers = int(spare_cpus * num_workers_per_device) + num_gpus_per_worker = 0 + num_cpus_per_worker = spare_cpus / num_workers + + # hyperparams + config.update({ + "train_batch_size": train_batch_size, + "horizon": 1000, + # "lr_schedule": + # [[0, hparams['lr_init']], + # [20000000, hparams['lr_final']]], + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + "entropy_coeff": hparams['entropy_coeff'], + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net", "use_lstm": True, + "lstm_cell_size": 128} + + }) + return algorithm, env_name, config + + +def main(unused_argv): + ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(1e10), + redis_max_memory=int(2e10)) + if FLAGS.env == 'harvest': + hparams = harvest_default_params + else: + hparams = cleanup_default_params + alg_run, env_name, config = setup(FLAGS.env, hparams, FLAGS.algorithm, + FLAGS.train_batch_size, + FLAGS.num_cpus, + FLAGS.num_gpus, FLAGS.num_agents, + FLAGS.use_gpus_for_workers, + FLAGS.use_gpu_for_driver, + FLAGS.num_workers_per_device) + + if FLAGS.exp_name is None: + exp_name = FLAGS.env + '_' + FLAGS.algorithm + else: + exp_name = FLAGS.exp_name + print('Commencing experiment', exp_name) + + run_experiments({ + exp_name: { + "run": alg_run, + "env": env_name, + "stop": { + "training_iteration": FLAGS.training_iterations + }, + 'checkpoint_freq': FLAGS.checkpoint_frequency, + "config": config, + 'upload_dir': 's3://njaques.experiments/first_reproduction/causal_basline' + + } + }) + + +if __name__ == '__main__': + tf.app.run(main) diff --git a/run_scripts/train_baseline_a3c.py b/run_scripts/train_baseline_a3c.py new file mode 100644 index 00000000..92659da9 --- /dev/null +++ b/run_scripts/train_baseline_a3c.py @@ -0,0 +1,195 @@ +import ray +from ray import tune +from ray.rllib.agents.registry import get_agent_class +from ray.rllib.agents.a3c.a3c_tf_policy_graph import A3CPolicyGraph +from ray.rllib.models import ModelCatalog +from ray.tune import run_experiments +from ray.tune.registry import register_env +import tensorflow as tf + +from social_dilemmas.envs.harvest import HarvestEnv +from social_dilemmas.envs.cleanup import CleanupEnv +from models.conv_to_fc_net import ConvToFCNet + + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string( + 'exp_name', 'train_baseline_a3c_cleanup', + 'Name of the ray_results experiment directory where results are stored.') +tf.app.flags.DEFINE_string( + 'env', 'cleanup', + 'Name of the environment to rollout. Can be cleanup or harvest.') +tf.app.flags.DEFINE_integer( + 'num_agents', 5, + 'Number of agent policies') +tf.app.flags.DEFINE_integer( + 'num_cpus', 14, + 'Number of available CPUs') +tf.app.flags.DEFINE_integer( + 'num_gpus', 0, + 'Number of available GPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpus_for_workers', False, + 'Set to true to run workers on GPUs rather than CPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpu_for_driver', False, + 'Set to true to run driver on GPU rather than CPU.') +tf.app.flags.DEFINE_float( + 'num_workers_per_device', 1, + 'Number of workers to place on a single device (CPU or GPU)') +tf.app.flags.DEFINE_boolean( + 'resume', False, + 'Set to true to resume a previously stopped experiment.') +tf.app.flags.DEFINE_boolean( + 'tune', True, + 'Set to true to tune hyperparameters.') + +harvest_default_params = { + 'lr_init': 0.00136, + 'lr_final': 0.000028, + 'entropy_coeff': .000687} + +cleanup_default_params = { + 'lr_init': 0.00126, + 'lr_final': 0.000012, + 'entropy_coeff': .00176} + + +def setup(env, hparams, num_cpus, num_gpus, num_agents, use_gpus_for_workers=False, + use_gpu_for_driver=False, num_workers_per_device=1, tune_hparams=False): + + if env == 'harvest': + def env_creator(_): + return HarvestEnv(num_agents=num_agents) + single_env = HarvestEnv() + hparams = harvest_default_params + else: + def env_creator(_): + return CleanupEnv(num_agents=num_agents) + single_env = CleanupEnv() + hparams = cleanup_default_params + + env_name = env + "_env" + register_env(env_name, env_creator) + + obs_space = single_env.observation_space + act_space = single_env.action_space + + # Each policy can have a different configuration (including custom model) + def gen_policy(): + return (A3CPolicyGraph, obs_space, act_space, {}) + + # Setup PPO with an ensemble of `num_policies` different policy graphs + policy_graphs = {} + for i in range(num_agents): + policy_graphs['agent-' + str(i)] = gen_policy() + + def policy_mapping_fn(agent_id): + return agent_id + + # register the custom model + model_name = "conv_to_fc_net" + ModelCatalog.register_custom_model(model_name, ConvToFCNet) + + algorithm = 'A3C' + agent_cls = get_agent_class(algorithm) + config = agent_cls._default_config.copy() + + # information for replay + config['env_config']['func_create'] = tune.function(env_creator) + config['env_config']['env_name'] = env_name + config['env_config']['run'] = algorithm + + # Calculate device configurations + gpus_for_driver = int(use_gpu_for_driver) + cpus_for_driver = 1 - gpus_for_driver + if use_gpus_for_workers: + spare_gpus = (num_gpus - gpus_for_driver) + num_workers = int(spare_gpus * num_workers_per_device) + num_gpus_per_worker = spare_gpus / num_workers + num_cpus_per_worker = 0 + else: + spare_cpus = (num_cpus - cpus_for_driver) + num_workers = int(spare_cpus * num_workers_per_device) + num_gpus_per_worker = 0 + num_cpus_per_worker = int(spare_cpus / num_workers) + + # hyperparams + if tune_hparams: + config.update({ + "train_batch_size": 128, + "horizon": 1000, + "lr_schedule": [[0, tune.grid_search([5e-4, 5e-3])], + [20000000, tune.grid_search([5e-4, 5e-5])]], + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + "entropy_coeff": tune.grid_search([0, -1e-1, -1e-2]), + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net", "use_lstm": True, + "lstm_cell_size": 128} + }) + else: + config.update({ + #"train_batch_size": 128, + "horizon": 1000, + # "lr_schedule": [[0, hparams['lr_init']], + # [20000000, hparams['lr_final']]], + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + # "entropy_coeff": hparams['entropy_coeff'], + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net", "use_lstm": True, + "lstm_cell_size": 128} + }) + return algorithm, env_name, config + + +def main(unused_argv): + # ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(10e10), + # redis_max_memory=int(20e10)) + ray.init(redis_address="localhost:6379") + if FLAGS.env == 'harvest': + hparams = harvest_default_params + else: + hparams = cleanup_default_params + alg_run, env_name, config = setup(FLAGS.env, hparams, FLAGS.num_cpus, + FLAGS.num_gpus, FLAGS.num_agents, + FLAGS.use_gpus_for_workers, + FLAGS.use_gpu_for_driver, + FLAGS.num_workers_per_device, FLAGS.tune) + + if FLAGS.exp_name is None: + exp_name = FLAGS.env + '_A3C' + else: + exp_name = FLAGS.exp_name + print('Commencing experiment', exp_name) + + run_experiments({ + exp_name: { + "run": alg_run, + "env": env_name, + "stop": { + "training_iteration": 20000 + }, + 'checkpoint_freq': 500, + "config": config, + 'upload_dir': 's3://njaques.experiments/sixth_reproduction/causal_influence_baseline_harvest' + } + }, resume=FLAGS.resume) + + +if __name__ == '__main__': + tf.app.run(main) diff --git a/run_scripts/train_baseline_a3c_actions.py b/run_scripts/train_baseline_a3c_actions.py new file mode 100644 index 00000000..c8d607df --- /dev/null +++ b/run_scripts/train_baseline_a3c_actions.py @@ -0,0 +1,209 @@ +import ray +from ray import tune +from ray.rllib.agents.registry import get_agent_class +from ray.rllib.agents.a3c.a3c_policy_graph_actions import A3CPolicyGraph +from ray.rllib.models import ModelCatalog +from ray.tune import run_experiments +from ray.tune.registry import register_env +import tensorflow as tf + +from social_dilemmas.envs.harvest import HarvestEnv +from social_dilemmas.envs.cleanup import CleanupEnv +from models.conv_to_fc_net_actions import ConvToFCNetActions + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string( + 'exp_name', 'causal_actions_visible_cleanup', + 'Name of the ray_results experiment directory where results are stored.') +tf.app.flags.DEFINE_string( + 'env', 'cleanup', + 'Name of the environment to rollout. Can be cleanup or harvest.') +tf.app.flags.DEFINE_integer( + 'num_agents', 5, + 'Number of agent policies') +tf.app.flags.DEFINE_integer( + 'num_cpus', 14, + 'Number of available CPUs') +tf.app.flags.DEFINE_integer( + 'num_gpus', 0, + 'Number of available GPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpus_for_workers', False, + 'Set to true to run workers on GPUs rather than CPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpu_for_driver', False, + 'Set to true to run driver on GPU rather than CPU.') +tf.app.flags.DEFINE_float( + 'num_workers_per_device', 1, + 'Number of workers to place on a single device (CPU or GPU)') +tf.app.flags.DEFINE_boolean( + 'tune', True, + 'Set to true to do hyperparameter tuning.') +tf.app.flags.DEFINE_boolean( + 'debug', False, + 'Set to true to run in a debugging / testing mode with less memory.') +tf.app.flags.DEFINE_boolean( + 'resume', False, + 'Set to true to resume a previously stopped experiment.') + +harvest_default_params = { + 'lr_init': 0.00136, + 'lr_final': 0.000028, + 'entropy_coeff': .000687} + +cleanup_default_params = { + 'lr_init': 0.00126, + 'lr_final': 0.000012, + 'entropy_coeff': .00176} + + +def setup(env, hparams, num_cpus, num_gpus, num_agents, use_gpus_for_workers=False, + use_gpu_for_driver=False, num_workers_per_device=1, tune_hparams=False): + if env == 'harvest': + def env_creator(_): + return HarvestEnv(num_agents=num_agents) + + single_env = HarvestEnv() + default_hparams = harvest_default_params + else: + def env_creator(_): + return CleanupEnv(num_agents=num_agents) + + single_env = CleanupEnv() + default_hparams = cleanup_default_params + + env_name = env + "_env" + register_env(env_name, env_creator) + + obs_space = single_env.observation_space + act_space = single_env.action_space + + # Each policy can have a different configuration (including custom model) + def gen_policy(agent_id): + return (A3CPolicyGraph, obs_space, act_space, + {'num_other_agents': num_agents - 1, 'agent_id': agent_id}) + + # Setup A3C with an ensemble of `num_policies` different policy graphs + policy_graphs = {} + for i in range(num_agents): + agent_id = 'agent-' + str(i) + policy_graphs[agent_id] = gen_policy(agent_id) + + def policy_mapping_fn(agent_id): + return agent_id + + # register the custom model + model_name = "conv_to_fc_net_actions" + ModelCatalog.register_custom_model(model_name, ConvToFCNetActions) + + algorithm = 'A3C' + agent_cls = get_agent_class(algorithm) + config = agent_cls._default_config.copy() + + # information for replay + config['env_config']['func_create'] = tune.function(env_creator) + config['env_config']['env_name'] = env_name + config['env_config']['run'] = algorithm + + # Calculate device configurations + gpus_for_driver = int(use_gpu_for_driver) + cpus_for_driver = 1 - gpus_for_driver + if use_gpus_for_workers: + spare_gpus = (num_gpus - gpus_for_driver) + num_workers = int(spare_gpus * num_workers_per_device) + num_gpus_per_worker = spare_gpus / num_workers + num_cpus_per_worker = 0 + else: + spare_cpus = (num_cpus - cpus_for_driver) + num_workers = int(spare_cpus * num_workers_per_device) + num_gpus_per_worker = 0 + num_cpus_per_worker = spare_cpus / num_workers + + # hyperparams + if tune_hparams: + config.update({ + "train_batch_size": 128, + "horizon": 1000, + "lr_schedule": [[0, tune.grid_search([5e-4, 5e-3])], + [20000000, tune.grid_search([5e-4, 5e-5, 5e-6])]], + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + "entropy_coeff": tune.grid_search([0, -1e-1, -1e-2]), + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net_actions", "use_lstm": True, + "lstm_cell_size": 128, "lstm_use_prev_action_reward": True, + "custom_options": {"num_other_agents": num_agents - 1}} + + }) + else: + config.update({ + "train_batch_size": 128, + "horizon": 1000, + "lr_schedule": [[0, default_hparams['lr_init']], + [20000000, default_hparams['lr_final']]], + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + "entropy_coeff": default_hparams['entropy_coeff'], + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net_actions", "use_lstm": True, + "lstm_cell_size": 128, "lstm_use_prev_action_reward": True, + "custom_options": {"num_other_agents": num_agents - 1}} + + }) + return algorithm, env_name, config + + +def main(unused_argv): + if FLAGS.debug: + # ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(10e10), + # redis_max_memory=int(50e10)) + ray.init(redis_address="localhost:6379") + else: + # ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(25e10), + # redis_max_memory=int(50e10)) + ray.init(redis_address="localhost:6379") + if FLAGS.env == 'harvest': + hparams = harvest_default_params + else: + hparams = cleanup_default_params + alg_run, env_name, config = setup(FLAGS.env, hparams, FLAGS.num_cpus, + FLAGS.num_gpus, FLAGS.num_agents, + FLAGS.use_gpus_for_workers, + FLAGS.use_gpu_for_driver, + FLAGS.num_workers_per_device, FLAGS.tune) + + if FLAGS.exp_name is None: + exp_name = FLAGS.env + '_A3C_actions' + else: + exp_name = FLAGS.exp_name + print('Commencing experiment', exp_name) + + run_experiments({ + exp_name: { + "run": alg_run, + "env": env_name, + "stop": { + "training_iteration": 20000 + }, + 'checkpoint_freq': 100, + "config": config, + 'upload_dir': 's3://njaques.experiments/fourth_reproduction/causal_actions_cleanup' + } + }, resume=FLAGS.resume) + + +if __name__ == '__main__': + tf.app.run(main) diff --git a/run_scripts/train_baseline_dqn.py b/run_scripts/train_baseline_dqn.py new file mode 100644 index 00000000..7d735431 --- /dev/null +++ b/run_scripts/train_baseline_dqn.py @@ -0,0 +1,166 @@ +import ray +from ray import tune +from ray.rllib.agents.registry import get_agent_class +from ray.rllib.agents.dqn.dqn_policy_graph import DQNPolicyGraph +from ray.rllib.models import ModelCatalog +from ray.tune import run_experiments +from ray.tune.registry import register_env +import tensorflow as tf + +from social_dilemmas.envs.harvest import HarvestEnv +from social_dilemmas.envs.cleanup import CleanupEnv +from models.conv_to_fc_net_no_lstm import ConvToFCNet + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string( + 'exp_name', None, + 'Name of the ray_results experiment directory where results are stored.') +tf.app.flags.DEFINE_string( + 'env', 'cleanup', + 'Name of the environment to rollout. Can be cleanup or harvest.') +tf.app.flags.DEFINE_integer( + 'num_agents', 5, + 'Number of agent policies') +tf.app.flags.DEFINE_integer( + 'num_cpus', 2, + 'Number of available CPUs') +tf.app.flags.DEFINE_integer( + 'num_gpus', 1, + 'Number of available GPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpus_for_workers', False, + 'Set to true to run workers on GPUs rather than CPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpu_for_driver', False, + 'Set to true to run driver on GPU rather than CPU.') +tf.app.flags.DEFINE_float( + 'num_workers_per_device', 1, + 'Number of workers to place on a single device (CPU or GPU)') +tf.app.flags.DEFINE_boolean( + 'resume', False, + 'Set to true to resume a previously stopped experiment.') + +harvest_default_params = { + 'lr_init': 0.00136, + 'lr_final': 0.000028, + 'entropy_coeff': -.000687} + +cleanup_default_params = { + 'lr_init': 0.00126, + 'lr_final': 0.000012, + 'entropy_coeff': -.00176} + + +def setup(env, hparams, num_cpus, num_gpus, num_agents, use_gpus_for_workers=False, + use_gpu_for_driver=False, num_workers_per_device=1): + if env == 'harvest': + def env_creator(_): + return HarvestEnv(num_agents=num_agents) + + single_env = HarvestEnv() + else: + def env_creator(_): + return CleanupEnv(num_agents=num_agents) + + single_env = CleanupEnv() + + env_name = env + "_env" + register_env(env_name, env_creator) + + obs_space = single_env.observation_space + act_space = single_env.action_space + + # Each policy can have a different configuration (including custom model) + def gen_policy(): + return (DQNPolicyGraph, obs_space, act_space, {}) + + # Setup PPO with an ensemble of `num_policies` different policy graphs + policy_graphs = {} + for i in range(num_agents): + policy_graphs['agent-' + str(i)] = gen_policy() + + def policy_mapping_fn(agent_id): + return agent_id + + # register the custom model + model_name = "conv_to_fc_net_no_lstm" + ModelCatalog.register_custom_model(model_name, ConvToFCNet) + + algorithm = 'DQN' + agent_cls = get_agent_class(algorithm) + config = agent_cls._default_config.copy() + + # information for replay + config['env_config']['func_create'] = tune.function(env_creator) + config['env_config']['env_name'] = env_name + config['env_config']['run'] = algorithm + + # Calculate device configurations + gpus_for_driver = int(use_gpu_for_driver) + cpus_for_driver = 1 - gpus_for_driver + if use_gpus_for_workers: + spare_gpus = (num_gpus - gpus_for_driver) + num_workers = int(spare_gpus * num_workers_per_device) + num_gpus_per_worker = spare_gpus / num_workers + num_cpus_per_worker = 0 + else: + spare_cpus = (num_cpus - cpus_for_driver) + num_workers = int(spare_cpus * num_workers_per_device) + num_gpus_per_worker = 0 + num_cpus_per_worker = spare_cpus / num_workers + + # hyperparams + config.update({ + "train_batch_size": 128, + "horizon": 1000, + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net_no_lstm", + "use_lstm": False, "lstm_cell_size": 128} + + }) + return algorithm, env_name, config + + +def main(unused_argv): + ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(2e10), + redis_max_memory=int(1e10)) + if FLAGS.env == 'harvest': + hparams = harvest_default_params + else: + hparams = cleanup_default_params + alg_run, env_name, config = setup(FLAGS.env, hparams, FLAGS.num_cpus, + FLAGS.num_gpus, FLAGS.num_agents, + FLAGS.use_gpus_for_workers, + FLAGS.use_gpu_for_driver, + FLAGS.num_workers_per_device) + + if FLAGS.exp_name is None: + exp_name = FLAGS.env + '_DQN' + else: + exp_name = FLAGS.exp_name + print('Commencing experiment', exp_name) + + run_experiments({ + exp_name: { + "run": alg_run, + "env": env_name, + "stop": { + "training_iteration": 300000 + }, + 'checkpoint_freq': 1000, + "config": config, + } + }, resume=FLAGS.resume) + + +if __name__ == '__main__': + tf.app.run(main) diff --git a/run_scripts/train_baseline_dqn_actions.py b/run_scripts/train_baseline_dqn_actions.py new file mode 100644 index 00000000..1987f71d --- /dev/null +++ b/run_scripts/train_baseline_dqn_actions.py @@ -0,0 +1,169 @@ +import ray +from ray import tune +from ray.rllib.agents.registry import get_agent_class +from ray.rllib.agents.causal_dqn.dqn_policy_graph import DQNPolicyGraph +from ray.rllib.models import ModelCatalog +from ray.tune import run_experiments +from ray.tune.registry import register_env +import tensorflow as tf + +from social_dilemmas.envs.harvest import HarvestEnv +from social_dilemmas.envs.cleanup import CleanupEnv +from models.conv_to_fc_net_actions_no_lstm import ConvToFCNetActions + +FLAGS = tf.app.flags.FLAGS + +tf.app.flags.DEFINE_string( + 'exp_name', None, + 'Name of the ray_results experiment directory where results are stored.') +tf.app.flags.DEFINE_string( + 'env', 'cleanup', + 'Name of the environment to rollout. Can be cleanup or harvest.') +tf.app.flags.DEFINE_integer( + 'num_agents', 5, + 'Number of agent policies') +tf.app.flags.DEFINE_integer( + 'num_cpus', 2, + 'Number of available CPUs') +tf.app.flags.DEFINE_integer( + 'num_gpus', 1, + 'Number of available GPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpus_for_workers', False, + 'Set to true to run workers on GPUs rather than CPUs') +tf.app.flags.DEFINE_boolean( + 'use_gpu_for_driver', False, + 'Set to true to run driver on GPU rather than CPU.') +tf.app.flags.DEFINE_float( + 'num_workers_per_device', 1, + 'Number of workers to place on a single device (CPU or GPU)') +tf.app.flags.DEFINE_boolean( + 'resume', False, + 'Set to true to resume a previously stopped experiment.') + +harvest_default_params = { + 'lr_init': 0.00136, + 'lr_final': 0.000028, + 'entropy_coeff': -.000687} + +cleanup_default_params = { + 'lr_init': 0.00126, + 'lr_final': 0.000012, + 'entropy_coeff': -.00176} + + +def setup(env, hparams, num_cpus, num_gpus, num_agents, use_gpus_for_workers=False, + use_gpu_for_driver=False, num_workers_per_device=1): + if env == 'harvest': + def env_creator(_): + return HarvestEnv(num_agents=num_agents) + + single_env = HarvestEnv() + else: + def env_creator(_): + return CleanupEnv(num_agents=num_agents) + + single_env = CleanupEnv() + + env_name = env + "_env" + register_env(env_name, env_creator) + + obs_space = single_env.observation_space + act_space = single_env.action_space + + # Each policy can have a different configuration (including custom model) + def gen_policy(agent_id): + return (DQNPolicyGraph, obs_space, act_space, + {'num_other_agents': num_agents - 1, 'agent_id': agent_id}) + + # Setup PPO with an ensemble of `num_policies` different policy graphs + policy_graphs = {} + for i in range(num_agents): + agent_id = 'agent-' + str(i) + policy_graphs[agent_id] = gen_policy(agent_id) + + def policy_mapping_fn(agent_id): + return agent_id + + # register the custom model + model_name = "conv_to_fc_net_actions" + ModelCatalog.register_custom_model(model_name, ConvToFCNetActions) + + algorithm = 'DQN' + agent_cls = get_agent_class(algorithm) + config = agent_cls._default_config.copy() + + # information for replay + config['env_config']['func_create'] = tune.function(env_creator) + config['env_config']['env_name'] = env_name + config['env_config']['run'] = algorithm + + # Calculate device configurations + gpus_for_driver = int(use_gpu_for_driver) + cpus_for_driver = 1 - gpus_for_driver + if use_gpus_for_workers: + spare_gpus = (num_gpus - gpus_for_driver) + num_workers = int(spare_gpus * num_workers_per_device) + num_gpus_per_worker = spare_gpus / num_workers + num_cpus_per_worker = 0 + else: + spare_cpus = (num_cpus - cpus_for_driver) + num_workers = int(spare_cpus * num_workers_per_device) + num_gpus_per_worker = 0 + num_cpus_per_worker = spare_cpus / num_workers + + # hyperparams + config.update({ + "train_batch_size": 128, + "horizon": 1000, + "num_workers": num_workers, + "num_gpus": gpus_for_driver, # The number of GPUs for the driver + "num_cpus_for_driver": cpus_for_driver, + "num_gpus_per_worker": num_gpus_per_worker, # Can be a fraction + "num_cpus_per_worker": num_cpus_per_worker, # Can be a fraction + "multiagent": { + "policy_graphs": policy_graphs, + "policy_mapping_fn": tune.function(policy_mapping_fn), + }, + "model": {"custom_model": "conv_to_fc_net_actions", + "lstm_cell_size": 128, "use_lstm": False, + "custom_options": {"num_other_agents": num_agents - 1}} + + }) + return algorithm, env_name, config + + +def main(unused_argv): + ray.init(num_cpus=FLAGS.num_cpus, object_store_memory=int(2e10), + redis_max_memory=int(1e10)) + if FLAGS.env == 'harvest': + hparams = harvest_default_params + else: + hparams = cleanup_default_params + alg_run, env_name, config = setup(FLAGS.env, hparams, FLAGS.num_cpus, + FLAGS.num_gpus, FLAGS.num_agents, + FLAGS.use_gpus_for_workers, + FLAGS.use_gpu_for_driver, + FLAGS.num_workers_per_device) + + if FLAGS.exp_name is None: + exp_name = FLAGS.env + '_DQN_actions' + else: + exp_name = FLAGS.exp_name + print('Commencing experiment', exp_name) + + run_experiments({ + exp_name: { + "run": alg_run, + "env": env_name, + "stop": { + "training_iteration": 300000 + }, + 'checkpoint_freq': 1000, + "config": config, + } + }, resume=FLAGS.resume) + + +if __name__ == '__main__': + tf.app.run(main) diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..472a8be2 --- /dev/null +++ b/setup.py @@ -0,0 +1,7 @@ +from setuptools import setup + + +setup( + name='causal=empathy', + version='0.0.1', +) diff --git a/social_dilemmas/__init__.py b/social_dilemmas/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/social_dilemmas/constants.py b/social_dilemmas/constants.py new file mode 100644 index 00000000..bf971c84 --- /dev/null +++ b/social_dilemmas/constants.py @@ -0,0 +1,93 @@ +# Scheme heavily adapted from https://github.com/deepmind/pycolab/ +# '@' means "wall" +# 'P' means "player" spawn point +# 'A' means apply spawn point +# '' is empty space + +HARVEST_MAP = [ + '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@', + '@ P P A P AAAAA P A P @', + '@ P A P AA P AAA A A @', + '@ A AAA AAA A A AA AAAA @', + '@ A AAA A A A AAA A A A A @', + '@AAA A A A AAA A AAA A P@', + '@ A A AAA AAA A A A AA AA AA @', + '@ A A AAA A A AAA AAA A @', + '@ AAA A AAA A AAAA @', + '@ P A A A AAA A A P @', + '@A AAA A A AAA A AAAA P @', + '@ A A AAA A A A AA A P @', + '@ AAA A A AAA AA AAA P @', + '@ A A AAA A P A @', + '@ P A P P P P @', + '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'] + +HARVEST_MAP_MEDIUM = [ + '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@', + '@ P P A P AAAAA P @', + '@ P A P AA P AAA @', + '@ A AAA AAA A A AA A@', + '@ A AAA A A A AAA A A @', + '@AAA A A A AAA A AAA @', + '@ A A AAA AAA A A A AA @', + '@ A A AAA A A AAA AAA @', + '@ AAA A AAA A AAAA @', + '@ P A A A AAA A A @', + '@A AAA A A AAA A AAAA @', + '@ A A AAA A A A AA @', + '@ P A P P P @', + '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'] + +HARVEST_MAP_SMALL = [ + '@@@@@@@@@@@@@@@@@@@@@@@@@@@@', + '@ P P A P AAAA P@', + '@ P A P A P AA @', + '@ A AAA AA A A @', + '@ A AA A A A AAA A @', + '@AAA A A A AA A AA @', + '@ A A AA AAA A A AA @', + '@ A A AAA A A AA AA@', + '@ AA A AAA A AAA@', + '@ P A A A AA A @', + '@ A A AA A A A @', + '@ P A P P@', + '@@@@@@@@@@@@@@@@@@@@@@@@@@@@'] + +HARVEST_MAP_SMALLER = [ + '@@@@@@@@@@@@@@@@@@@@@@', + '@ P P A P AA@', + '@ P A P AA P @', + '@ A AAA AAA A @', + '@ A AAA A A AAA @', + '@AAA A A A AAA @', + '@ A A AAA AAA A A @', + '@ A A AAA A A AA@', + '@ P A A A AAA @', + '@@@@@@@@@@@@@@@@@@@@@@'] + +CLEANUP_MAP = [ + '@@@@@@@@@@@@@@@@@@', + '@RRRRRR BBBBB@', + '@HHHHHH BBBB@', + '@RRRRRR BBBBB@', + '@RRRRR P BBBB@', + '@RRRRR P BBBBB@', + '@HHHHH BBBB@', + '@RRRRR BBBBB@', + '@HHHHHHSSSSSSBBBB@', + '@HHHHHHSSSSSSBBBB@', + '@RRRRR P P BBBB@', + '@HHHHH P BBBBB@', + '@RRRRRR P BBBB@', + '@HHHHHH P BBBBB@', + '@RRRRR BBBB@', + '@HHHH P BBBBB@', + '@RRRRR BBBB@', + '@HHHHH P P BBBBB@', + '@RRRRR BBBB@', + '@HHHH BBBBB@', + '@RRRRR BBBB@', + '@HHHHH BBBBB@', + '@RRRRR BBBB@', + '@HHHH BBBBB@', + '@@@@@@@@@@@@@@@@@@'] diff --git a/social_dilemmas/envs/__init__.py b/social_dilemmas/envs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/social_dilemmas/envs/agent.py b/social_dilemmas/envs/agent.py new file mode 100644 index 00000000..ea8f25b3 --- /dev/null +++ b/social_dilemmas/envs/agent.py @@ -0,0 +1,246 @@ +"""Base class for an agent that defines the possible actions. """ + +from gym.spaces import Box +from gym.spaces import Discrete +import numpy as np +import utility_funcs as util + +# basic moves every agent should do +BASE_ACTIONS = {0: 'MOVE_LEFT', # Move left + 1: 'MOVE_RIGHT', # Move right + 2: 'MOVE_UP', # Move up + 3: 'MOVE_DOWN', # Move down + 4: 'STAY', # don't move + 5: 'TURN_CLOCKWISE', # Rotate counter clockwise + 6: 'TURN_COUNTERCLOCKWISE'} # Rotate clockwise + + +class Agent(object): + + def __init__(self, agent_id, start_pos, start_orientation, grid, row_size, col_size): + """Superclass for all agents. + + Parameters + ---------- + agent_id: (str) + a unique id allowing the map to identify the agents + start_pos: (np.ndarray) + a 2d array indicating the x-y position of the agents + start_orientation: (np.ndarray) + a 2d array containing a unit vector indicating the agent direction + grid: (2d array) + a reference to this agent's view of the environment + row_size: (int) + how many rows up and down the agent can look + col_size: (int) + how many columns left and right the agent can look + """ + self.agent_id = agent_id + self.pos = np.array(start_pos) + self.orientation = start_orientation + # TODO(ev) change grid to env, this name is not very informative + self.grid = grid + self.row_size = row_size + self.col_size = col_size + self.reward_this_turn = 0 + + @property + def action_space(self): + """Identify the dimensions and bounds of the action space. + + MUST BE implemented in new environments. + + Returns + ------- + gym Box, Discrete, or Tuple type + a bounded box depicting the shape and bounds of the action space + """ + raise NotImplementedError + + @property + def observation_space(self): + """Identify the dimensions and bounds of the observation space. + + MUST BE implemented in new environments. + + Returns + ------- + gym Box, Discrete or Tuple type + a bounded box depicting the shape and bounds of the observation + space + """ + raise NotImplementedError + + def action_map(self, action_number): + """Maps action_number to a desired action in the map""" + raise NotImplementedError + + def get_state(self): + return util.return_view(self.grid, self.get_pos(), + self.row_size, self.col_size) + + def compute_reward(self): + reward = self.reward_this_turn + self.reward_this_turn = 0 + return reward + + def set_pos(self, new_pos): + self.pos = np.array(new_pos) + + def get_pos(self): + return self.pos + + def translate_pos_to_egocentric_coord(self, pos): + offset_pos = pos - self.get_pos() + ego_centre = [self.row_size, self.col_size] + return ego_centre + offset_pos + + def set_orientation(self, new_orientation): + self.orientation = new_orientation + + def get_orientation(self): + return self.orientation + + def get_map(self): + return self.grid + + def return_valid_pos(self, new_pos): + """Checks that the next pos is legal, if not return current pos""" + ego_new_pos = new_pos # self.translate_pos_to_egocentric_coord(new_pos) + new_row, new_col = ego_new_pos + # you can't walk through walls + temp_pos = new_pos.copy() + if self.grid[new_row, new_col] == '@': + temp_pos = self.get_pos() + return temp_pos + + def update_agent_pos(self, new_pos): + """Updates the agents internal positions + + Returns + ------- + old_pos: (np.ndarray) + 2 element array describing where the agent used to be + new_pos: (np.ndarray) + 2 element array describing the agent positions + """ + old_pos = self.get_pos() + ego_new_pos = new_pos # self.translate_pos_to_egocentric_coord(new_pos) + new_row, new_col = ego_new_pos + # you can't walk through walls + temp_pos = new_pos.copy() + if self.grid[new_row, new_col] == '@': + temp_pos = self.get_pos() + self.set_pos(temp_pos) + # TODO(ev) list array consistency + return self.get_pos(), np.array(old_pos) + + def update_agent_rot(self, new_rot): + self.set_orientation(new_rot) + + def hit(self, char): + """Defines how an agent responds to being hit by a beam of type char""" + raise NotImplementedError + + def consume(self, char): + """Defines how an agent interacts with the char it is standing on""" + raise NotImplementedError + + +HARVEST_ACTIONS = BASE_ACTIONS.copy() +HARVEST_ACTIONS.update({7: 'FIRE'}) # Fire a penalty beam + +HARVEST_VIEW_SIZE = 7 + + +class HarvestAgent(Agent): + + def __init__(self, agent_id, start_pos, start_orientation, grid, view_len=HARVEST_VIEW_SIZE): + self.view_len = view_len + super().__init__(agent_id, start_pos, start_orientation, grid, view_len, view_len) + self.update_agent_pos(start_pos) + self.update_agent_rot(start_orientation) + + @property + def action_space(self): + return Discrete(8) + + # Ugh, this is gross, this leads to the actions basically being + # defined in two places + def action_map(self, action_number): + """Maps action_number to a desired action in the map""" + return HARVEST_ACTIONS[action_number] + + @property + def observation_space(self): + return Box(low=0, high=1.0, shape=(2 * self.view_len + 1, + 2 * self.view_len + 1, 3), dtype=np.float32) + + def hit(self, char): + if char == 'F': + self.reward_this_turn -= 50 + + def fire_beam(self, char): + if char == 'F': + self.reward_this_turn -= 1 + + def get_done(self): + return False + + def consume(self, char): + """Defines how an agent interacts with the char it is standing on""" + if char == 'A': + self.reward_this_turn += 1 + return ' ' + else: + return char + + +CLEANUP_ACTIONS = BASE_ACTIONS.copy() +CLEANUP_ACTIONS.update({7: 'FIRE', # Fire a penalty beam + 8: 'CLEAN'}) # Fire a cleaning beam + +CLEANUP_VIEW_SIZE = 7 + + +class CleanupAgent(Agent): + def __init__(self, agent_id, start_pos, start_orientation, grid, view_len=CLEANUP_VIEW_SIZE): + self.view_len = view_len + super().__init__(agent_id, start_pos, start_orientation, grid, view_len, view_len) + # remember what you've stepped on + self.update_agent_pos(start_pos) + self.update_agent_rot(start_orientation) + + @property + def action_space(self): + return Discrete(9) + + @property + def observation_space(self): + return Box(low=0.0, high=1.0, shape=(2 * self.view_len + 1, + 2 * self.view_len + 1, 3), dtype=np.float32) + + # Ugh, this is gross, this leads to the actions basically being + # defined in two places + def action_map(self, action_number): + """Maps action_number to a desired action in the map""" + return CLEANUP_ACTIONS[action_number] + + def fire_beam(self, char): + if char == 'F': + self.reward_this_turn -= 1 + + def get_done(self): + return False + + def hit(self, char): + if char == 'F': + self.reward_this_turn -= 50 + + def consume(self, char): + """Defines how an agent interacts with the char it is standing on""" + if char == 'A': + self.reward_this_turn += 1 + return ' ' + else: + return char diff --git a/social_dilemmas/envs/cleanup.py b/social_dilemmas/envs/cleanup.py new file mode 100644 index 00000000..eb2d08ab --- /dev/null +++ b/social_dilemmas/envs/cleanup.py @@ -0,0 +1,168 @@ +import numpy as np +import random + +from social_dilemmas.constants import CLEANUP_MAP +from social_dilemmas.envs.map_env import MapEnv, ACTIONS +from social_dilemmas.envs.agent import CleanupAgent # CLEANUP_VIEW_SIZE + +# Add custom actions to the agent +ACTIONS['FIRE'] = 5 # length of firing beam +ACTIONS['CLEAN'] = 5 # length of cleanup beam + +# Custom colour dictionary +CLEANUP_COLORS = {'C': [100, 255, 255], # Cyan cleaning beam + 'S': [113, 75, 24], # Light grey-blue stream cell + 'H': [99, 156, 194], # brown waste cells + 'R': [113, 75, 24]} # Light grey-blue river cell + +SPAWN_PROB = [0, 0.005, 0.02, 0.05] + +thresholdDepletion = 0.4 +thresholdRestoration = 0.0 +wasteSpawnProbability = 0.5 +appleRespawnProbability = 0.05 + + +class CleanupEnv(MapEnv): + + def __init__(self, ascii_map=CLEANUP_MAP, num_agents=1, render=False): + super().__init__(ascii_map, num_agents, render) + + # compute potential waste area + unique, counts = np.unique(self.base_map, return_counts=True) + counts_dict = dict(zip(unique, counts)) + self.potential_waste_area = counts_dict.get('H', 0) + counts_dict.get('R', 0) + self.current_apple_spawn_prob = appleRespawnProbability + self.current_waste_spawn_prob = wasteSpawnProbability + self.compute_probabilities() + + # make a list of the potential apple and waste spawn points + self.apple_points = [] + self.waste_start_points = [] + self.waste_points = [] + self.river_points = [] + self.stream_points = [] + for row in range(self.base_map.shape[0]): + for col in range(self.base_map.shape[1]): + if self.base_map[row, col] == 'P': + self.spawn_points.append([row, col]) + elif self.base_map[row, col] == 'B': + self.apple_points.append([row, col]) + elif self.base_map[row, col] == 'S': + self.stream_points.append([row, col]) + if self.base_map[row, col] == 'H': + self.waste_start_points.append([row, col]) + if self.base_map[row, col] == 'H' or self.base_map[row, col] == 'R': + self.waste_points.append([row, col]) + if self.base_map[row, col] == 'R': + self.river_points.append([row, col]) + + self.color_map.update(CLEANUP_COLORS) + + @property + def action_space(self): + agents = list(self.agents.values()) + return agents[0].action_space + + @property + def observation_space(self): + # FIXME(ev) this is an information leak + agents = list(self.agents.values()) + return agents[0].observation_space + + def custom_reset(self): + """Initialize the walls and the waste""" + for waste_start_point in self.waste_start_points: + self.world_map[waste_start_point[0], waste_start_point[1]] = 'H' + for river_point in self.river_points: + self.world_map[river_point[0], river_point[1]] = 'R' + for stream_point in self.stream_points: + self.world_map[stream_point[0], stream_point[1]] = 'S' + self.compute_probabilities() + + def custom_action(self, agent, action): + """Allows agents to take actions that are not move or turn""" + updates = [] + if action == 'FIRE': + agent.fire_beam('F') + updates = self.update_map_fire(agent.get_pos().tolist(), + agent.get_orientation(), ACTIONS['FIRE'], + fire_char='F') + elif action == 'CLEAN': + agent.fire_beam('C') + updates = self.update_map_fire(agent.get_pos().tolist(), + agent.get_orientation(), + ACTIONS['FIRE'], + fire_char='C', + cell_types=['H'], + update_char=['R'], + blocking_cells=['H']) + return updates + + def custom_map_update(self): + """"Update the probabilities and then spawn""" + self.compute_probabilities() + self.update_map(self.spawn_apples_and_waste()) + + def setup_agents(self): + """Constructs all the agents in self.agent""" + map_with_agents = self.get_map_with_agents() + + for i in range(self.num_agents): + agent_id = 'agent-' + str(i) + spawn_point = self.spawn_point() + rotation = self.spawn_rotation() + # grid = util.return_view(map_with_agents, spawn_point, + # CLEANUP_VIEW_SIZE, CLEANUP_VIEW_SIZE) + # agent = CleanupAgent(agent_id, spawn_point, rotation, grid) + agent = CleanupAgent(agent_id, spawn_point, rotation, map_with_agents) + self.agents[agent_id] = agent + + def spawn_apples_and_waste(self): + spawn_points = [] + # spawn apples, multiple can spawn per step + for i in range(len(self.apple_points)): + row, col = self.apple_points[i] + # don't spawn apples where agents already are + if [row, col] not in self.agent_pos and self.world_map[row, col] != 'A': + rand_num = np.random.rand(1)[0] + if rand_num < self.current_apple_spawn_prob: + spawn_points.append((row, col, 'A')) + + # spawn one waste point, only one can spawn per step + if not np.isclose(self.current_waste_spawn_prob, 0): + random.shuffle(self.waste_points) + for i in range(len(self.waste_points)): + row, col = self.waste_points[i] + # don't spawn waste where it already is + if self.world_map[row, col] != 'H': + rand_num = np.random.rand(1)[0] + if rand_num < self.current_waste_spawn_prob: + spawn_points.append((row, col, 'H')) + break + return spawn_points + + def compute_probabilities(self): + waste_density = 0 + if self.potential_waste_area > 0: + waste_density = 1 - self.compute_permitted_area() / self.potential_waste_area + if waste_density >= thresholdDepletion: + self.current_apple_spawn_prob = 0 + self.current_waste_spawn_prob = 0 + else: + self.current_waste_spawn_prob = wasteSpawnProbability + if waste_density <= thresholdRestoration: + self.current_apple_spawn_prob = appleRespawnProbability + else: + spawn_prob = (1 - (waste_density - thresholdRestoration) + / (thresholdDepletion - thresholdRestoration)) \ + * appleRespawnProbability + self.current_apple_spawn_prob = spawn_prob + + def compute_permitted_area(self): + """How many cells can we spawn waste on?""" + unique, counts = np.unique(self.world_map, return_counts=True) + counts_dict = dict(zip(unique, counts)) + current_area = counts_dict.get('H', 0) + free_area = self.potential_waste_area - current_area + return free_area diff --git a/social_dilemmas/envs/harvest.py b/social_dilemmas/envs/harvest.py new file mode 100644 index 00000000..053620e7 --- /dev/null +++ b/social_dilemmas/envs/harvest.py @@ -0,0 +1,128 @@ +import numpy as np +import copy + +import os.path +from pprint import pprint +import sys + +from social_dilemmas.envs.agent import HarvestAgent # HARVEST_VIEW_SIZE +from social_dilemmas.constants import HARVEST_MAP + +from social_dilemmas.envs.map_env import MapEnv, ACTIONS + +APPLE_RADIUS = 2 + +# Add custom actions to the agent +ACTIONS['FIRE'] = 5 # length of firing range + +SPAWN_PROB = [0, 0.005, 0.02, 0.05] + + +class HarvestEnv(MapEnv): + def __init__(self, ascii_map=HARVEST_MAP, num_agents=1, render=False): + super().__init__(ascii_map, num_agents, render) + self.apple_points = [] + for row in range(self.base_map.shape[0]): + for col in range(self.base_map.shape[1]): + if self.base_map[row, col] == 'A': + self.apple_points.append([row, col]) + + @property + def action_space(self): + agents = list(self.agents.values()) + return agents[0].action_space + + @property + def observation_space(self): + agents = list(self.agents.values()) + return agents[0].observation_space + + def setup_agents(self): + map_with_agents = self.get_map_with_agents() + + for i in range(self.num_agents): + agent_id = 'agent-' + str(i) + spawn_point = self.spawn_point() + rotation = self.spawn_rotation() + grid = map_with_agents + agent = HarvestAgent(agent_id, spawn_point, rotation, grid) + # grid = util.return_view(map_with_agents, spawn_point, + # HARVEST_VIEW_SIZE, HARVEST_VIEW_SIZE) + # agent = HarvestAgent(agent_id, spawn_point, rotation, grid) + self.agents[agent_id] = agent + + def custom_reset(self): + """Initialize the walls and the apples""" + for apple_point in self.apple_points: + self.world_map[apple_point[0], apple_point[1]] = 'A' + + def custom_action(self, agent, action): + agent.fire_beam('F') + updates = self.update_map_fire(agent.get_pos().tolist(), + agent.get_orientation(), + ACTIONS['FIRE'], fire_char='F') + return updates + + def custom_map_update(self): + "See parent class" + # spawn the apples + new_apples = self.spawn_apples() + self.update_map(new_apples) + + save_path = '/home/gds38/Desktop/storage/' + completeName = os.path.join(save_path, "stored_info_apple.txt") + with open(completeName, "a") as app_out: + pprint( self.count_apples(self.world_map), app_out ) + + save_path = '/home/gds38/Desktop/storage/' + completeName = os.path.join(save_path, "stored_info_final_apple.txt") + with open(completeName, "w") as app_out: + pprint( self.count_apples(self.world_map), app_out ) + + #print(self.count_apples(self.world_map)) + + def spawn_apples(self): + """Construct the apples spawned in this step. + + Returns + ------- + new_apple_points: list of 2-d lists + a list containing lists indicating the spawn positions of new apples + """ + apple_prob_sum = 0 + apple_prob_count = 0 + + new_apple_points = [] + for i in range(len(self.apple_points)): + row, col = self.apple_points[i] + # apples can't spawn where agents are standing or where an apple already is + if [row, col] not in self.agent_pos and self.world_map[row, col] != 'A': + num_apples = 0 + for j in range(-APPLE_RADIUS, APPLE_RADIUS + 1): + for k in range(-APPLE_RADIUS, APPLE_RADIUS + 1): + if j**2 + k**2 <= APPLE_RADIUS: + x, y = self.apple_points[i] + if 0 <= x + j < self.world_map.shape[0] and \ + self.world_map.shape[1] > y + k >= 0: + symbol = self.world_map[x + j, y + k] + if symbol == 'A': + num_apples += 1 + + spawn_prob = SPAWN_PROB[min(num_apples, 3)] + + apple_prob_sum += spawn_prob + apple_prob_count += 1 + + rand_num = np.random.rand(1)[0] + if rand_num < spawn_prob: + new_apple_points.append((row, col, 'A')) + + return new_apple_points + + def count_apples(self, window): + # compute how many apples are in window + unique, counts = np.unique(window, return_counts=True) + counts_dict = dict(zip(unique, counts)) + num_apples = counts_dict.get('A', 0) + del window + return num_apples diff --git a/social_dilemmas/envs/map_env.py b/social_dilemmas/envs/map_env.py new file mode 100644 index 00000000..4554ed0b --- /dev/null +++ b/social_dilemmas/envs/map_env.py @@ -0,0 +1,764 @@ +"""Base map class that defines the rendering process +""" +import random + +import matplotlib.pyplot as plt +import numpy as np +from ray.rllib.env import MultiAgentEnv + +import copy + +ACTIONS = {'MOVE_LEFT': [0, -1], # Move left + 'MOVE_RIGHT': [0, 1], # Move right + 'MOVE_UP': [-1, 0], # Move up + 'MOVE_DOWN': [1, 0], # Move down + 'STAY': [0, 0], # don't move + 'TURN_CLOCKWISE': [[0, 1], [-1, 0]], # Clockwise rotation matrix + 'TURN_COUNTERCLOCKWISE': [[0, -1], [1, 0]]} # Counter clockwise rotation matrix + # Positive Theta is in the counterclockwise direction + +ORIENTATIONS = {'LEFT': [0, -1], + 'RIGHT': [0, 1], + 'UP': [-1, 0], + 'DOWN': [1, 0]} + +DEFAULT_COLOURS = {' ': [0, 0, 0], # Black background + '0': [0, 0, 0], # Black background beyond map walls + '': [180, 180, 180], # Grey board walls + '@': [180, 180, 180], # Grey board walls + 'A': [0, 255, 0], # Green apples + 'F': [255, 255, 0], # Yellow fining beam + 'P': [159, 67, 255], # Purple player + + # Colours for agents. R value is a unique identifier + '1': [159, 67, 255], # Purple + '2': [2, 81, 154], # Blue + '3': [204, 0, 204], # Magenta + '4': [216, 30, 54], # Red + '5': [254, 151, 0], # Orange + '6': [100, 255, 255], # Cyan + '7': [99, 99, 255], # Lavender + '8': [250, 204, 255], # Pink + '9': [238, 223, 16]} # Yellow + + +# the axes look like this when printed out +# graphic is here just to resolve the difference between directions in row-column space +# and in visual space +# ^ +# | +# U +# P +# <--LEFT * RIGHT----> +# D +# O +# W +# N +# | + +class MapEnv(MultiAgentEnv): + + def __init__(self, ascii_map, num_agents=1, render=True, color_map=None): + """ + + Parameters + ---------- + ascii_map: list of strings + Specify what the map should look like. Look at constant.py for + further explanation + num_agents: int + Number of agents to have in the system. + render: bool + Whether to render the environment + color_map: dict + Specifies how to convert between ascii chars and colors + """ + self.num_agents = num_agents + self.base_map = self.ascii_to_numpy(ascii_map) + # map without agents or beams + self.world_map = np.full((len(self.base_map), len(self.base_map[0])), ' ') + #self.saved_map_iteration = np.full((len(self.base_map), len(self.base_map[0])), 'p') + + self.beam_pos = [] + + self.agents = {} + + # returns the agent at a desired position if there is one + self.pos_dict = {} + self.color_map = color_map if color_map is not None else DEFAULT_COLOURS + self.spawn_points = [] # where agents can appear + + self.wall_points = [] + for row in range(self.base_map.shape[0]): + for col in range(self.base_map.shape[1]): + if self.base_map[row, col] == 'P': + self.spawn_points.append([row, col]) + elif self.base_map[row, col] == '@': + self.wall_points.append([row, col]) + self.setup_agents() + + def custom_reset(self): + """Reset custom elements of the map. For example, spawn apples and build walls""" + pass + + def custom_action(self, agent, action): + """Execute any custom actions that may be defined, like fire or clean + + Parameters + ---------- + agent: agent that is taking the action + action: key of the action to be taken + + Returns + ------- + updates: list(list(row, col, char)) + List of cells to place onto the map + """ + pass + + def custom_map_update(self): + """Custom map updates that don't have to do with agent actions""" + pass + + def setup_agents(self): + """Construct all the agents for the environment""" + raise NotImplementedError + + # FIXME(ev) move this to a utils eventually + def ascii_to_numpy(self, ascii_list): + """converts a list of strings into a numpy array + + + Parameters + ---------- + ascii_list: list of strings + List describing what the map should look like + Returns + ------- + arr: np.ndarray + numpy array describing the map with ' ' indicating an empty space + """ + + arr = np.full((len(ascii_list), len(ascii_list[0])), ' ') + for row in range(arr.shape[0]): + for col in range(arr.shape[1]): + arr[row, col] = ascii_list[row][col] + return arr + + def step(self, actions): + """Takes in a dict of actions and converts them to a map update + + Parameters + ---------- + actions: dict {agent-id: int} + dict of actions, keyed by agent-id that are passed to the agent. The agent + interprets the int and converts it to a command + + Returns + ------- + observations: dict of arrays representing agent observations + rewards: dict of rewards for each agent + dones: dict indicating whether each agent is done + info: dict to pass extra info to gym + """ + + self.beam_pos = [] + agent_actions = {} + for agent_id, action in actions.items(): + agent_action = self.agents[agent_id].action_map(action) + agent_actions[agent_id] = agent_action + + # move + self.update_moves(agent_actions) + + for agent in self.agents.values(): + pos = agent.get_pos() + new_char = agent.consume(self.world_map[pos[0], pos[1]]) + self.world_map[pos[0], pos[1]] = new_char + + # execute custom moves like firing + self.update_custom_moves(agent_actions) + + # execute spawning events + self.custom_map_update() + + map_with_agents = self.get_map_with_agents() + + observations = {} + rewards = {} + dones = {} + info = {} + for agent in self.agents.values(): + agent.grid = map_with_agents + rgb_arr = self.map_to_colors(agent.get_state(), self.color_map) + rgb_arr = self.rotate_view(agent.orientation, rgb_arr) + rgb_arr = rgb_arr/255.0 + observations[agent.agent_id] = rgb_arr + rewards[agent.agent_id] = agent.compute_reward() + dones[agent.agent_id] = agent.get_done() + + # Add other agent visibility + if agent.agent_id not in info: + info[agent.agent_id] = {} + info[agent.agent_id]['visible_agents'] = \ + self.find_visible_agents(agent.agent_id) + + dones["__all__"] = np.any(list(dones.values())) + return observations, rewards, dones, info + + def reset(self): + """Reset the environment. + + This method is performed in between rollouts. It resets the state of + the environment. + + Returns + ------- + observation: dict of numpy ndarray + the initial observation of the space. The initial reward is assumed + to be zero. + """ + self.beam_pos = [] + self.agents = {} + self.setup_agents() + self.reset_map() + self.custom_map_update() + + map_with_agents = self.get_map_with_agents() + + observations = {} + for agent in self.agents.values(): + agent.grid = map_with_agents + # agent.grid = util.return_view(map_with_agents, agent.pos, + # agent.row_size, agent.col_size) + rgb_arr = self.map_to_colors(agent.get_state(), self.color_map) + observations[agent.agent_id] = rgb_arr + return observations + + @property + def agent_pos(self): + return [agent.get_pos().tolist() for agent in self.agents.values()] + + # This method is just used for testing + # FIXME(ev) move into the testing class + @property + + def test_map(self): + """Gets a version of the environment map where generic + 'P' characters have been replaced with specific agent IDs. + + Returns: + 2D array of strings representing the map. + """ + grid = np.copy(self.world_map) + + for agent_id, agent in self.agents.items(): + # If agent is not within map, skip. + if not (agent.pos[0] >= 0 and agent.pos[0] < grid.shape[0] and + agent.pos[1] >= 0 and agent.pos[1] < grid.shape[1]): + continue + + grid[agent.pos[0], agent.pos[1]] = 'P' + + for beam_pos in self.beam_pos: + grid[beam_pos[0], beam_pos[1]] = beam_pos[2] + + return grid + + def get_map_with_agents(self): + """Gets a version of the environment map where generic + 'P' characters have been replaced with specific agent IDs. + + Returns: + 2D array of strings representing the map. + """ + grid = np.copy(self.world_map) + + for agent_id, agent in self.agents.items(): + char_id = str(int(agent_id[-1]) + 1) + + # If agent is not within map, skip. + if not (agent.pos[0] >= 0 and agent.pos[0] < grid.shape[0] and + agent.pos[1] >= 0 and agent.pos[1] < grid.shape[1]): + continue + + grid[agent.pos[0], agent.pos[1]] = char_id + + # beams should overlay agents + for beam_pos in self.beam_pos: + grid[beam_pos[0], beam_pos[1]] = beam_pos[2] + + return grid + + def check_agent_map(self, agent_map): + """Checks the map to make sure agents aren't duplicated""" + unique, counts = np.unique(agent_map, return_counts=True) + count_dict = dict(zip(unique, counts)) + + # check for multiple agents + for i in range(self.num_agents): + if count_dict[str(i + 1)] != 1: + print('Error! Wrong number of agent', i, 'in map!') + return False + return True + + def map_to_colors(self, map=None, color_map=None): + """Converts a map to an array of RGB values. + Parameters + ---------- + map: np.ndarray + map to convert to colors + color_map: dict + mapping between array elements and desired colors + Returns + ------- + arr: np.ndarray + 3-dim numpy array consisting of color map + """ + if map is None: + map = self.get_map_with_agents() + if color_map is None: + color_map = self.color_map + + rgb_arr = np.zeros((map.shape[0], map.shape[1], 3), dtype=int) + for row_elem in range(map.shape[0]): + for col_elem in range(map.shape[1]): + rgb_arr[row_elem, col_elem, :] = color_map[map[row_elem, col_elem]] + + return rgb_arr + + def find_visible_agents(self, agent_id): + """Returns all the agents that can be seen by agent with agent_id + + Args + ---- + agent_id: str + The id of the agent whose visible agents we are asking about + + Returns + ------- + visible_agents: list + which agents can be seen by the agent with id "agent_id" + + """ + agent_pos = self.agents[agent_id].get_pos() + upper_lim = int(agent_pos[0] + self.agents[agent_id].row_size) + lower_lim = int(agent_pos[0] - self.agents[agent_id].row_size) + left_lim = int(agent_pos[1] - self.agents[agent_id].col_size) + right_lim = int(agent_pos[1] + self.agents[agent_id].col_size) + + other_agent_pos = [(agent.get_pos(), other_agent_id) for other_agent_id, agent in + self.agents.items() if other_agent_id != agent_id] + return [agent_tup[1] for agent_tup in other_agent_pos if + (lower_lim <= agent_tup[0][0] <= upper_lim + and left_lim <= agent_tup[0][1] <= right_lim)] + + def render(self, filename=None): + """ Creates an image of the map to plot or save. + + Args: + filename: If a string is passed, will save the image + to disk at this location. + """ + map_with_agents = self.get_map_with_agents() + + rgb_arr = self.map_to_colors(map_with_agents) + plt.imshow(rgb_arr, interpolation='nearest') + if filename is None: + plt.show() + else: + plt.savefig(filename) + + def update_moves(self, agent_actions): + """Converts agent action tuples into a new map and new agent positions. + Also resolves conflicts over multiple agents wanting a cell. + + This method works by finding all conflicts over a cell and randomly assigning them + to one of the agents that desires the slot. It then sets all of the other agents + that wanted the cell to have a move of staying. For moves that do not directly + conflict with another agent for a cell, but may not be temporarily resolvable + due to an agent currently being in the desired cell, we continually loop through + the actions until all moves have been satisfied or deemed impossible. + For example, agent 1 may want to move from [1,2] to [2,2] but agent 2 is in [2,2]. + Agent 2, however, is moving into [3,2]. Agent-1's action is first in the order so at the + first pass it is skipped but agent-2 moves to [3,2]. In the second pass, agent-1 will + then be able to move into [2,2]. + + Parameters + ---------- + agent_actions: dict + dict with agent_id as key and action as value + """ + + reserved_slots = [] + for agent_id, action in agent_actions.items(): + agent = self.agents[agent_id] + selected_action = ACTIONS[action] + # TODO(ev) these two parts of the actions + if 'MOVE' in action or 'STAY' in action: + # rotate the selected action appropriately + rot_action = self.rotate_action(selected_action, agent.get_orientation()) + new_pos = agent.get_pos() + rot_action + # allow the agents to confirm what position they can move to + new_pos = agent.return_valid_pos(new_pos) + reserved_slots.append((*new_pos, 'P', agent_id)) + elif 'TURN' in action: + new_rot = self.update_rotation(action, agent.get_orientation()) + agent.update_agent_rot(new_rot) + + # now do the conflict resolution part of the process + + # helpful for finding the agent in the conflicting slot + agent_by_pos = {tuple(agent.get_pos()): agent.agent_id for agent in self.agents.values()} + + # agent moves keyed by ids + agent_moves = {} + + # lists of moves and their corresponding agents + move_slots = [] + agent_to_slot = [] + + for slot in reserved_slots: + row, col = slot[0], slot[1] + if slot[2] == 'P': + agent_id = slot[3] + agent_moves[agent_id] = [row, col] + move_slots.append([row, col]) + agent_to_slot.append(agent_id) + + # cut short the computation if there are no moves + if len(agent_to_slot) > 0: + + # first we will resolve all slots over which multiple agents + # want the slot + + # shuffle so that a random agent has slot priority + shuffle_list = list(zip(agent_to_slot, move_slots)) + np.random.shuffle(shuffle_list) + agent_to_slot, move_slots = zip(*shuffle_list) + unique_move, indices, return_count = np.unique(move_slots, return_index=True, + return_counts=True, axis=0) + search_list = np.array(move_slots) + + # first go through and remove moves that can't possible happen. Three types + # 1. Trying to move into an agent that has been issued a stay command + # 2. Trying to move into the spot of an agent that doesn't have a move + # 3. Two agents trying to walk through one another + + # Resolve all conflicts over a space + if np.any(return_count > 1): + for move, index, count in zip(unique_move, indices, return_count): + if count > 1: + # check that the cell you are fighting over doesn't currently + # contain an agent that isn't going to move for one of the agents + # If it does, all the agents commands should become STAY + # since no moving will be possible + conflict_indices = np.where((search_list == move).all(axis=1))[0] + all_agents_id = [agent_to_slot[i] for i in conflict_indices] + # all other agents now stay in place so update their moves + # to reflect this + conflict_cell_free = True + for agent_id in all_agents_id: + moves_copy = agent_moves.copy() + # TODO(ev) code duplication, simplify + if move.tolist() in self.agent_pos: + # find the agent that is currently at that spot and make sure + # that the move is possible. If it won't be, remove it. + conflicting_agent_id = agent_by_pos[tuple(move)] + curr_pos = self.agents[agent_id].get_pos().tolist() + curr_conflict_pos = self.agents[conflicting_agent_id]. \ + get_pos().tolist() + conflict_move = agent_moves.get(conflicting_agent_id, + curr_conflict_pos) + # Condition (1): + # a STAY command has been issued + if agent_id == conflicting_agent_id: + conflict_cell_free = False + # Condition (2) + # its command is to stay + # or you are trying to move into an agent that hasn't + # received a command + elif conflicting_agent_id not in moves_copy.keys() or \ + curr_conflict_pos == conflict_move: + conflict_cell_free = False + + # Condition (3) + # It is trying to move into you and you are moving into it + elif conflicting_agent_id in moves_copy.keys(): + if agent_moves[conflicting_agent_id] == curr_pos and \ + move.tolist() == self.agents[conflicting_agent_id] \ + .get_pos().tolist(): + conflict_cell_free = False + + # if the conflict cell is open, let one of the conflicting agents + # move into it + if conflict_cell_free: + self.agents[agent_to_slot[index]].update_agent_pos(move) + agent_by_pos = {tuple(agent.get_pos()): + agent.agent_id for agent in self.agents.values()} + # ------------------------------------ + # remove all the other moves that would have conflicted + remove_indices = np.where((search_list == move).all(axis=1))[0] + all_agents_id = [agent_to_slot[i] for i in remove_indices] + # all other agents now stay in place so update their moves + # to stay in place + for agent_id in all_agents_id: + agent_moves[agent_id] = self.agents[agent_id].get_pos().tolist() + + # make the remaining un-conflicted moves + while len(agent_moves.items()) > 0: + agent_by_pos = {tuple(agent.get_pos()): + agent.agent_id for agent in self.agents.values()} + num_moves = len(agent_moves.items()) + moves_copy = agent_moves.copy() + del_keys = [] + for agent_id, move in moves_copy.items(): + if agent_id in del_keys: + continue + if move in self.agent_pos: + # find the agent that is currently at that spot and make sure + # that the move is possible. If it won't be, remove it. + conflicting_agent_id = agent_by_pos[tuple(move)] + curr_pos = self.agents[agent_id].get_pos().tolist() + curr_conflict_pos = self.agents[conflicting_agent_id].get_pos().tolist() + conflict_move = agent_moves.get(conflicting_agent_id, curr_conflict_pos) + # Condition (1): + # a STAY command has been issued + if agent_id == conflicting_agent_id: + del agent_moves[agent_id] + del_keys.append(agent_id) + # Condition (2) + # its command is to stay + # or you are trying to move into an agent that hasn't received a command + elif conflicting_agent_id not in moves_copy.keys() or \ + curr_conflict_pos == conflict_move: + del agent_moves[agent_id] + del_keys.append(agent_id) + # Condition (3) + # It is trying to move into you and you are moving into it + elif conflicting_agent_id in moves_copy.keys(): + if agent_moves[conflicting_agent_id] == curr_pos and \ + move == self.agents[conflicting_agent_id].get_pos().tolist(): + del agent_moves[conflicting_agent_id] + del agent_moves[agent_id] + del_keys.append(agent_id) + del_keys.append(conflicting_agent_id) + # this move is unconflicted so go ahead and move + else: + self.agents[agent_id].update_agent_pos(move) + del agent_moves[agent_id] + del_keys.append(agent_id) + + # no agent is able to move freely, so just move them all + # no updates to hidden cells are needed since all the + # same cells will be covered + if len(agent_moves) == num_moves: + for agent_id, move in agent_moves.items(): + self.agents[agent_id].update_agent_pos(move) + break + + def update_custom_moves(self, agent_actions): + for agent_id, action in agent_actions.items(): + # check its not a move based action + if 'MOVE' not in action and 'STAY' not in action and 'TURN' not in action: + agent = self.agents[agent_id] + updates = self.custom_action(agent, action) + if len(updates) > 0: + self.update_map(updates) + + def update_map(self, new_points): + """For points in new_points, place desired char on the map""" + for i in range(len(new_points)): + row, col, char = new_points[i] + self.world_map[row, col] = char + + def reset_map(self): + """Resets the map to be empty as well as a custom reset set by subclasses""" + self.world_map = np.full((len(self.base_map), len(self.base_map[0])), ' ') + self.build_walls() + self.custom_reset() + + def update_map_fire(self, firing_pos, firing_orientation, fire_len, fire_char, cell_types=[], + update_char=[], blocking_cells='P'): + """From a firing position, fire a beam that may clean or hit agents + + Notes: + (1) Beams are blocked by agents + (2) A beam travels along until it hits a blocking cell at which beam the beam + covers that cell and stops + (3) If a beam hits a cell whose character is in cell_types, it replaces it with + the corresponding index in update_char + (4) As per the rules, the beams fire from in front of the agent and on its + sides so the beam that starts in front of the agent travels out one + cell further than it does along the sides. + (5) This method updates the beam_pos, an internal representation of how + which cells need to be rendered with fire_char in the agent view + + Parameters + ---------- + firing_pos: (list) + the row, col from which the beam is fired + firing_orientation: (list) + the direction the beam is to be fired in + fire_len: (int) + the number of cells forward to fire + fire_char: (str) + the cell that should be placed where the beam goes + cell_types: (list of str) + the cells that are affected by the beam + update_char: (list of str) + the character that should replace the affected cells. + blocking_cells: (list of str) + cells that block the firing beam + Returns + ------- + updates: (tuple (row, col, char)) + the cells that have been hit by the beam and what char will be placed there + """ + agent_by_pos = {tuple(agent.get_pos()): agent_id for agent_id, agent in self.agents.items()} + start_pos = np.asarray(firing_pos) + firing_direction = ORIENTATIONS[firing_orientation] + # compute the other two starting positions + right_shift = self.rotate_right(firing_direction) + firing_pos = [start_pos, start_pos + right_shift - firing_direction, + start_pos - right_shift - firing_direction] + firing_points = [] + updates = [] + for pos in firing_pos: + next_cell = pos + firing_direction + for i in range(fire_len): + if self.test_if_in_bounds(next_cell) and \ + self.world_map[next_cell[0], next_cell[1]] != '@': + + # FIXME(ev) code duplication + # agents absorb beams + # activate the agents hit function if needed + if [next_cell[0], next_cell[1]] in self.agent_pos: + agent_id = agent_by_pos[(next_cell[0], next_cell[1])] + self.agents[agent_id].hit(fire_char) + firing_points.append((next_cell[0], next_cell[1], fire_char)) + if self.world_map[next_cell[0], next_cell[1]] in cell_types: + type_index = cell_types.index(self.world_map[next_cell[0], + next_cell[1]]) + updates.append((next_cell[0], next_cell[1], update_char[type_index])) + break + + # update the cell if needed + if self.world_map[next_cell[0], next_cell[1]] in cell_types: + type_index = cell_types.index(self.world_map[next_cell[0], next_cell[1]]) + updates.append((next_cell[0], next_cell[1], update_char[type_index])) + + firing_points.append((next_cell[0], next_cell[1], fire_char)) + + # check if the cell blocks beams. For example, waste blocks beams. + if self.world_map[next_cell[0], next_cell[1]] in blocking_cells: + break + + # increment the beam position + next_cell += firing_direction + + else: + break + + self.beam_pos += firing_points + return updates + + def spawn_point(self): + """Returns a randomly selected spawn point.""" + spawn_index = 0 + is_free_cell = False + curr_agent_pos = [agent.get_pos().tolist() for agent in self.agents.values()] + random.shuffle(self.spawn_points) + for i, spawn_point in enumerate(self.spawn_points): + if [spawn_point[0], spawn_point[1]] not in curr_agent_pos: + spawn_index = i + is_free_cell = True + assert is_free_cell, 'There are not enough spawn points! Check your map?' + return np.array(self.spawn_points[spawn_index]) + + def spawn_rotation(self): + """Return a randomly selected initial rotation for an agent""" + rand_int = np.random.randint(len(ORIENTATIONS.keys())) + return list(ORIENTATIONS.keys())[rand_int] + + def rotate_view(self, orientation, view): + """Takes a view of the map and rotates it the agent orientation + + Parameters + ---------- + orientation: str + str in {'UP', 'LEFT', 'DOWN', 'RIGHT'} + view: np.ndarray (row, column, channel) + + Returns + ------- + a rotated view + """ + if orientation == 'UP': + return view + elif orientation == 'LEFT': + return np.rot90(view, k=1, axes=(0, 1)) + elif orientation == 'DOWN': + return np.rot90(view, k=2, axes=(0, 1)) + elif orientation == 'RIGHT': + return np.rot90(view, k=3, axes=(0, 1)) + else: + raise ValueError('Orientation {} is not valid'.format(orientation)) + + def build_walls(self): + for i in range(len(self.wall_points)): + row, col = self.wall_points[i] + self.world_map[row, col] = '@' + + ######################################## + # Utility methods, move these eventually + ######################################## + + # TODO(ev) this can be a general property of map_env or a util + def rotate_action(self, action_vec, orientation): + # WARNING: Note, we adopt the physics convention that \theta=0 is in the +y direction + if orientation == 'UP': + return action_vec + elif orientation == 'LEFT': + return self.rotate_left(action_vec) + elif orientation == 'RIGHT': + return self.rotate_right(action_vec) + else: + return self.rotate_left(self.rotate_left(action_vec)) + + def rotate_left(self, action_vec): + return np.dot(ACTIONS['TURN_COUNTERCLOCKWISE'], action_vec) + + def rotate_right(self, action_vec): + return np.dot(ACTIONS['TURN_CLOCKWISE'], action_vec) + + # TODO(ev) this should be an agent property + def update_rotation(self, action, curr_orientation): + if action == 'TURN_COUNTERCLOCKWISE': + if curr_orientation == 'LEFT': + return 'DOWN' + elif curr_orientation == 'DOWN': + return 'RIGHT' + elif curr_orientation == 'RIGHT': + return 'UP' + else: + return 'LEFT' + else: + if curr_orientation == 'LEFT': + return 'UP' + elif curr_orientation == 'UP': + return 'RIGHT' + elif curr_orientation == 'RIGHT': + return 'DOWN' + else: + return 'LEFT' + + # TODO(ev) this definitely should go into utils or the general agent class + def test_if_in_bounds(self, pos): + """Checks if a selected cell is outside the range of the map""" + if pos[0] < 0 or pos[0] >= self.world_map.shape[0]: + return False + elif pos[1] < 0 or pos[1] >= self.world_map.shape[1]: + return False + else: + return True diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/cleanup_trajectory.mp4 b/tests/cleanup_trajectory.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..e4a1fff156556c9bfca865492f6e7f9dc8f93232 GIT binary patch literal 29307 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 B@nHY} literal 0 HcmV?d00001 diff --git a/tests/test_envs.py b/tests/test_envs.py new file mode 100644 index 00000000..9414920b --- /dev/null +++ b/tests/test_envs.py @@ -0,0 +1,1313 @@ +'''Unit tests for all of the envs''' + +import unittest + +import numpy as np +import random + +from gym.spaces import Discrete +from social_dilemmas.envs.agent import Agent +from social_dilemmas.envs.agent import CleanupAgent +from social_dilemmas.envs.agent import HarvestAgent +from social_dilemmas.envs.agent import BASE_ACTIONS +from social_dilemmas.envs.agent import HARVEST_ACTIONS +from social_dilemmas.envs.agent import CLEANUP_ACTIONS +from social_dilemmas.envs.cleanup import CleanupEnv +from social_dilemmas.envs.harvest import HarvestEnv +from social_dilemmas.envs.map_env import MapEnv + +import utility_funcs as util + +# map actions to appropriate numbers +ACTION_MAP = {y: x for x, y in BASE_ACTIONS.items()} +HARVEST_ACTION_MAP = {y: x for x, y in HARVEST_ACTIONS.items()} +CLEANUP_ACTION_MAP = {y: x for x, y in CLEANUP_ACTIONS.items()} + +# Maps for any env +BASE_MAP_1 = [ + '@@@@@@@', + '@ @', + '@ @', + '@ @', + '@ @', + '@ @', + '@@@@@@@' +] +TEST_MAP_1 = np.array( + [['@'] * 7, + ['@'] + [' '] * 5 + ['@'], + ['@'] + [' '] * 5 + ['@'], + ['@'] + [' '] * 5 + ['@'], + ['@'] + [' '] * 5 + ['@'], + ['@'] + [' '] * 5 + ['@'], + ['@'] * 7] +) + +FIRE_RANGE_MAP = np.array( + [['@'] * 13, + ['@'] + [' '] * 11 + ['@'], + ['@'] + [' '] * 11 + ['@'], + ['@'] + [' '] * 11 + ['@'], + ['@'] + [' '] * 11 + ['@'], + ['@'] + [' '] * 11 + ['@'], + ['@'] + [' '] * 11 + ['@'], + ['@'] + [' '] * 11 + ['@'], + ['@'] + [' '] * 11 + ['@'], + ['@'] + [' '] * 11 + ['@'], + ['@'] + [' '] * 11 + ['@'], + ['@'] + [' '] * 11 + ['@'], + ['@'] * 13] +) + +# basic empty map with no apples +BASE_MAP_2 = [ + '@@@@@@', + '@ P @', + '@ @', + '@ @', + '@ P@', + '@@@@@@' +] +TEST_MAP_2 = np.array( + [['@'] * 6, + ['@'] + [' '] * 4 + ['@'], + ['@'] + [' '] * 4 + ['@'], + ['@'] + [' '] * 4 + ['@'], + ['@'] + [' '] * 2 + ['A'] + [' '] + ['@'], + ['@'] * 6] +) + +# Maps for Harvest +MINI_HARVEST_MAP = [ + '@@@@@@', + '@ P @', + '@ AA@', + '@ AA@', + '@ AP@', + '@@@@@@', +] + +# Maps for Cleanup +MINI_CLEANUP_MAP = [ + '@@@@@@', + '@ P @', + '@H BB@', + '@R BB@', + '@S BP@', + '@@@@@@', +] + +# Map to check that cleanup beam removes waste correctly +FIRING_CLEANUP_MAP = [ + '@@@@@@', + '@ @', + '@HHP @', + '@RH @', + '@H P @', + '@@@@@@', +] + +# Check that apples spawn correctly in cleanup +APPLE_SPAWN_MAP_CLEANUP = [ + '@@@@@@', + '@ P @', + '@ BB@', + '@ BB@', + '@ BP@', + '@@@@@@', +] + +# Check that the spawn probabilities are correct in cleanup +# Map to check that cleanup beam removes waste correctly +CLEANUP_PROB_MAP = [ + '@@@@@@', + '@ @', + '@HHPB@', + '@RH B@', + '@H PB@', + '@@@@@@', +] + + +# maps used to test different spawn positions and apple positions + + +class DummyMapEnv(MapEnv): + """This class implements a few missing methods in map env that are needed for testing.""" + + def setup_agents(self): + map_with_agents = self.get_map_with_agents() + + for i in range(self.num_agents): + agent_id = 'agent-' + str(i) + spawn_point = self.spawn_point() + rotation = self.spawn_rotation() + grid = map_with_agents + # grid = util.return_view(map_with_agents, spawn_point, + # 2, 2) + agent = DummyAgent(agent_id, spawn_point, rotation, grid, 2, 2) + self.agents[agent_id] = agent + + def execute_custom_reservations(self): + return + + +class DummyAgent(Agent): + def reward_from_pos(self, new_pos): + return 0 + + def get_done(self): + return False + + def action_map(self, action_number): + return BASE_ACTIONS[action_number] + + @property + def action_space(self): + return Discrete(len(ACTION_MAP)) + + def consume(self, char): + return char + + +class TestMapEnv(unittest.TestCase): + def tearDown(self): + """Remove the env""" + self.env = None + + def test_step(self): + """Just check that the step method works at all for all possible actions""" + self.env = DummyMapEnv(ascii_map=BASE_MAP_2, num_agents=1) + self.env.reset() + agents = list(self.env.agents.values()) + action_dim = agents[0].action_space.n + for i in range(action_dim): + self.env.step({'agent-0': i}) + + def test_walls(self): + """Check that the spawned map and base map have walls in the right place""" + self.env = DummyMapEnv(BASE_MAP_1, num_agents=0) + self.env.reset() + np.testing.assert_array_equal(self.env.base_map[0, :], np.array(['@'] * 7)) + np.testing.assert_array_equal(self.env.base_map[-1, :], np.array(['@'] * 7)) + np.testing.assert_array_equal(self.env.base_map[:, 0], np.array(['@'] * 7)) + np.testing.assert_array_equal(self.env.base_map[:, -1], np.array(['@'] * 7)) + + np.testing.assert_array_equal(self.env.world_map[0, :], np.array(['@'] * 7)) + np.testing.assert_array_equal(self.env.world_map[-1, :], np.array(['@'] * 7)) + np.testing.assert_array_equal(self.env.world_map[:, 0], np.array(['@'] * 7)) + np.testing.assert_array_equal(self.env.world_map[:, -1], np.array(['@'] * 7)) + + def test_view(self): + """Confirm that an agent placed at the right point returns the right view""" + agent_id = 'agent-0' + self.construct_map(TEST_MAP_1, agent_id, [3, 3], 'UP') + + def convert_empty_cells(view): + """Change all empty cells marked with '0' to '' for consistency.""" + view[view == '0'] = '' + return view + + # check if the view is correct if there are no walls + agent_view = self.env.agents[agent_id].get_state() + expected_view = np.array( + [[' '] * 5, + [' '] * 5, + [' '] * 2 + ['1'] + [' '] * 2, + [' '] * 5, + [' '] * 5] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + # check if the view is correct if the top wall is just in view + self.move_agent(agent_id, [2, 3]) + agent_view = self.env.agents[agent_id].get_state() + expected_view = np.array( + [['@'] * 5, + [' '] * 5, + [' '] * 2 + ['1'] + [' '] * 2, + [' '] * 5, + [' '] * 5] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + # check if if the view is correct if the view exceeds the top view + self.move_agent(agent_id, [1, 3]) + agent_view = self.env.agents[agent_id].get_state() + agent_view = convert_empty_cells(agent_view) + expected_view = np.array( + [[''] * 5, + ['@'] * 5, + [' '] * 2 + ['1'] + [' '] * 2, + [' '] * 5, + [' '] * 5] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + # check if the view is correct if the left wall is just in view + self.move_agent(agent_id, [3, 2]) + agent_view = self.env.agents[agent_id].get_state() + expected_view = np.array( + [['@'] + [' '] * 4, + ['@'] + [' '] * 4, + ['@'] + [' '] + ['1'] + [' '] * 2, + ['@'] + [' '] * 4, + ['@'] + [' '] * 4] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + # check if if the view is correct if the view exceeds the left view + self.move_agent(agent_id, [3, 1]) + agent_view = self.env.agents[agent_id].get_state() + agent_view = convert_empty_cells(agent_view) + expected_view = np.array( + [[''] + ['@'] + [' '] * 3, + [''] + ['@'] + [' '] * 3, + [''] + ['@'] + ['1'] + [' '] * 2, + [''] + ['@'] + [' '] * 3, + [''] + ['@'] + [' '] * 3] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + # check if the view is correct if the bot wall is just in view + self.move_agent(agent_id, [4, 3]) + agent_view = self.env.agents[agent_id].get_state() + expected_view = np.array( + [[' '] * 5, + [' '] * 5, + [' '] * 2 + ['1'] + [' '] * 2, + [' '] * 5, + ['@'] * 5] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + # check if if the view is correct if the view exceeds the bot view + self.move_agent(agent_id, [5, 3]) + agent_view = self.env.agents[agent_id].get_state() + agent_view = convert_empty_cells(agent_view) + expected_view = np.array( + [[' '] * 5, + [' '] * 5, + [' '] * 2 + ['1'] + [' '] * 2, + ['@'] * 5, + [''] * 5] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + # check if the view is correct if the right wall is just in view + self.move_agent(agent_id, [3, 4]) + agent_view = self.env.agents[agent_id].get_state() + expected_view = np.array( + [[' '] * 4 + ['@'], + [' '] * 4 + ['@'], + [' '] * 2 + ['1'] + [' '] + ['@'], + [' '] * 4 + ['@'], + [' '] * 4 + ['@']] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + # check if if the view is correct if the view exceeds the right view + self.move_agent(agent_id, [3, 5]) + agent_view = self.env.agents[agent_id].get_state() + agent_view = convert_empty_cells(agent_view) + expected_view = np.array( + [[' '] * 3 + ['@'] + [''], + [' '] * 3 + ['@'] + [''], + [' '] * 2 + ['1'] + ['@'] + [''], + [' '] * 3 + ['@'] + [''], + [' '] * 3 + ['@'] + ['']] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + # check if if the view is correct if the agent is in the bottom right corner + self.move_agent(agent_id, [5, 5]) + agent_view = self.env.agents[agent_id].get_state() + agent_view = convert_empty_cells(agent_view) + expected_view = np.array( + [[' '] * 3 + ['@'] + [''], + [' '] * 3 + ['@'] + [''], + [' '] * 2 + ['1'] + ['@'] + [''], + ['@'] * 4 + [''], + [''] * 5] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + def test_agent_actions(self): + # set up the map + agent_id = 'agent-0' + self.construct_map(TEST_MAP_1.copy(), agent_id, [2, 2], 'LEFT') + + # Test that all the moves and rotations work correctly + # test when facing left + self.env.step({agent_id: ACTION_MAP['MOVE_LEFT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [3, 2]) + self.env.step({agent_id: ACTION_MAP['MOVE_RIGHT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 2]) + self.env.step({agent_id: ACTION_MAP['MOVE_UP']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 1]) + self.env.step({agent_id: ACTION_MAP['MOVE_DOWN']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 2]) + # test when facing up + self.rotate_agent(agent_id, 'UP') + self.env.step({agent_id: ACTION_MAP['MOVE_LEFT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 1]) + self.env.step({agent_id: ACTION_MAP['MOVE_RIGHT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 2]) + self.env.step({agent_id: ACTION_MAP['MOVE_UP']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [1, 2]) + self.env.step({agent_id: ACTION_MAP['MOVE_DOWN']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 2]) + # test when facing down + self.rotate_agent(agent_id, 'DOWN') + self.env.step({agent_id: ACTION_MAP['MOVE_LEFT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 3]) + self.env.step({agent_id: ACTION_MAP['MOVE_RIGHT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 2]) + self.env.step({agent_id: ACTION_MAP['MOVE_UP']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [3, 2]) + self.env.step({agent_id: ACTION_MAP['MOVE_DOWN']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 2]) + # test when facing right + self.rotate_agent(agent_id, 'RIGHT') + self.env.step({agent_id: ACTION_MAP['MOVE_LEFT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [1, 2]) + self.env.step({agent_id: ACTION_MAP['MOVE_RIGHT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 2]) + self.env.step({agent_id: ACTION_MAP['MOVE_UP']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 3]) + self.env.step({agent_id: ACTION_MAP['MOVE_DOWN']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 2]) + + # check that stay works properly + self.env.step({agent_id: ACTION_MAP['STAY']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 2]) + self.assertEqual(self.env.test_map[2, 2], 'P') + + # quick test of stay + self.env.step({agent_id: ACTION_MAP['STAY']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 2]) + + # if an agent tries to move through a wall they should stay in the same place + # we check that this works correctly for both corner and non-corner edges + self.rotate_agent(agent_id, 'UP') + self.move_agent(agent_id, [1, 1]) + self.env.step({agent_id: ACTION_MAP['MOVE_UP']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [1, 1]) + self.env.step({agent_id: ACTION_MAP['MOVE_LEFT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [1, 1]) + self.move_agent(agent_id, [4, 4]) + self.env.step({agent_id: ACTION_MAP['MOVE_RIGHT']}) + self.env.step({agent_id: ACTION_MAP['MOVE_DOWN']}) + self.env.step({agent_id: ACTION_MAP['MOVE_RIGHT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [5, 5]) + self.env.step({agent_id: ACTION_MAP['MOVE_DOWN']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [5, 5]) + self.env.step({agent_id: ACTION_MAP['MOVE_LEFT']}) + self.env.step({agent_id: ACTION_MAP['MOVE_DOWN']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [5, 4]) + self.move_agent(agent_id, [4, 5]) + self.env.step({agent_id: ACTION_MAP['MOVE_RIGHT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [4, 5]) + self.move_agent(agent_id, [2, 1]) + self.env.step({agent_id: ACTION_MAP['MOVE_LEFT']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [2, 1]) + self.move_agent(agent_id, [1, 2]) + self.env.step({agent_id: ACTION_MAP['MOVE_UP']}) + np.testing.assert_array_equal(self.env.agents[agent_id].get_pos(), [1, 2]) + + # rotations correctly update the agent state + self.rotate_agent(agent_id, 'UP') + # clockwise + self.env.step({agent_id: ACTION_MAP['TURN_CLOCKWISE']}) + self.assertEqual('RIGHT', self.env.agents[agent_id].get_orientation()) + self.env.step({agent_id: ACTION_MAP['TURN_CLOCKWISE']}) + self.assertEqual('DOWN', self.env.agents[agent_id].get_orientation()) + self.env.step({agent_id: ACTION_MAP['TURN_CLOCKWISE']}) + self.assertEqual('LEFT', self.env.agents[agent_id].get_orientation()) + self.env.step({agent_id: ACTION_MAP['TURN_CLOCKWISE']}) + self.assertEqual('UP', self.env.agents[agent_id].get_orientation()) + + # counterclockwise + self.env.step({agent_id: ACTION_MAP['TURN_COUNTERCLOCKWISE']}) + self.assertEqual('LEFT', self.env.agents[agent_id].get_orientation()) + self.env.step({agent_id: ACTION_MAP['TURN_COUNTERCLOCKWISE']}) + self.assertEqual('DOWN', self.env.agents[agent_id].get_orientation()) + self.env.step({agent_id: ACTION_MAP['TURN_COUNTERCLOCKWISE']}) + self.assertEqual('RIGHT', self.env.agents[agent_id].get_orientation()) + self.env.step({agent_id: ACTION_MAP['TURN_COUNTERCLOCKWISE']}) + self.assertEqual('UP', self.env.agents[agent_id].get_orientation()) + + def test_agent_conflict(self): + '''Test that agent conflicts are correctly resolved''' + + # test that if there are two agents and two spawning points, they hit both of them + self.env = DummyMapEnv(ascii_map=BASE_MAP_2, num_agents=2) + self.env.reset() + np.testing.assert_array_equal(self.env.base_map, self.env.test_map) + + # test that agents can't walk into other agents + self.move_agent('agent-0', [3, 3]) + self.move_agent('agent-1', [3, 4]) + self.rotate_agent('agent-0', 'UP') + self.rotate_agent('agent-1', 'UP') + self.env.step({'agent-0': ACTION_MAP['MOVE_RIGHT']}) + self.env.step({'agent-1': ACTION_MAP['MOVE_LEFT']}) + np.testing.assert_array_equal(self.env.agents['agent-0'].get_pos(), [3, 3]) + np.testing.assert_array_equal(self.env.agents['agent-1'].get_pos(), [3, 4]) + + # test that agents can't walk through each other if they move simultaneously + self.env.step({'agent-0': ACTION_MAP['MOVE_RIGHT'], + 'agent-1': ACTION_MAP['MOVE_LEFT']}) + np.testing.assert_array_equal(self.env.agents['agent-0'].get_pos(), [3, 3]) + np.testing.assert_array_equal(self.env.agents['agent-1'].get_pos(), [3, 4]) + # also check that the map looks correct, no agent has disappeared + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', 'P', 'P', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + # test that agents can walk into other agents if moves are de-conflicting + # conflict only occurs stochastically so try it 50 times + np.random.seed(1) + for i in range(100): + self.env.step({'agent-0': ACTION_MAP['MOVE_RIGHT'], + 'agent-1': ACTION_MAP['MOVE_UP']}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', 'P', '@'], + ['@', ' ', ' ', ' ', 'P', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + self.env.step({'agent-0': ACTION_MAP['MOVE_LEFT'], + 'agent-1': ACTION_MAP['MOVE_DOWN']}) + + # test that if two agents have a conflicting move then the tie is broken randomly + num_agent_1 = 0.0 + num_agent_2 = 0.0 + for i in range(100): + self.move_agent('agent-0', [3, 2]) + self.move_agent('agent-1', [3, 4]) + self.env.step({'agent-0': ACTION_MAP['MOVE_RIGHT'], + 'agent-1': ACTION_MAP['MOVE_LEFT']}) + if self.env.agents['agent-0'].get_pos().tolist() == [3, 3]: + num_agent_1 += 1 + else: + num_agent_2 += 1 + # Also check that the map looks correct + expect_1 = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', 'P', 'P', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + expect_2 = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', 'P', 'P', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + equal_1 = np.array_equal(self.env.test_map, expect_1) + equal_2 = np.array_equal(self.env.test_map, expect_2) + self.assertTrue(equal_1 or equal_2) + agent_1_percent = num_agent_1 / (num_agent_1 + num_agent_2) + with_expected_val = (.53 == agent_1_percent) or (.47 == agent_1_percent) + self.assertTrue(with_expected_val) + + # check that this works correctly with three agents + self.add_agent('agent-2', [2, 3], 'UP', self.env, 3) + num_agent_1 = 0.0 + other_agents = 0.0 + for i in range(100): + self.move_agent('agent-0', [3, 2]) + self.move_agent('agent-1', [3, 4]) + self.move_agent('agent-2', [2, 3]) + self.env.step({'agent-0': ACTION_MAP['MOVE_RIGHT'], + 'agent-1': ACTION_MAP['MOVE_LEFT'], + 'agent-2': ACTION_MAP['MOVE_DOWN']}) + if self.env.agents['agent-2'].get_pos().tolist() == [3, 3]: + num_agent_1 += 1 + else: + other_agents += 1 + # Also check that the map looks correct + expect_1 = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', 'P', ' ', '@'], + ['@', ' ', 'P', 'P', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + expect_2 = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', 'P', 'P', 'P', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + expect_3 = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', 'P', ' ', '@'], + ['@', ' ', ' ', 'P', 'P', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + equal_1 = np.array_equal(self.env.test_map, expect_1) + equal_2 = np.array_equal(self.env.test_map, expect_2) + equal_3 = np.array_equal(self.env.test_map, expect_3) + self.assertTrue(equal_1 or equal_2 or equal_3) + agent_1_percent = num_agent_1 / (num_agent_1 + other_agents) + within_bounds = (agent_1_percent > .27) and (agent_1_percent < .39) + self.assertTrue(within_bounds) + + # you try to move into an agent that is in conflict with another agent + # fifty percent of the time you should succeed + percent_accomplished = 0 + percent_failed = 0 + for i in range(100): + self.move_agent('agent-1', [3, 4]) + self.move_agent('agent-2', [2, 2]) + self.move_agent('agent-0', [3, 2]) + self.env.step({'agent-0': ACTION_MAP['MOVE_RIGHT'], + 'agent-1': ACTION_MAP['MOVE_LEFT'], + 'agent-2': ACTION_MAP['MOVE_DOWN']}) + if self.env.agents['agent-2'].get_pos().tolist() == [2, 2]: + percent_failed += 1 + expect_1 = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', 'P', ' ', ' ', '@'], + ['@', ' ', 'P', 'P', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expect_1, self.env.test_map) + else: + percent_accomplished += 1 + expect_1 = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', 'P', 'P', 'P', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expect_1, self.env.test_map) + percent_success = percent_accomplished / (percent_accomplished + percent_failed) + within_bounds = (.40 < percent_success) and (percent_success < .60) + self.assertTrue(within_bounds) + + # Check that if there is more than one conflict simultaneously + # that it is handled correctly + agent_0_percent = 0 + agent_1_percent = 0 + num_trials = 100 + self.add_agent('agent-3', [1, 4], 'UP', self.env, 3) + for i in range(num_trials): + self.move_agent('agent-1', [3, 4]) + self.move_agent('agent-2', [1, 2]) + self.move_agent('agent-0', [3, 2]) + self.move_agent('agent-3', [1, 4]) + self.env.step({'agent-0': ACTION_MAP['MOVE_UP'], + 'agent-2': ACTION_MAP['MOVE_DOWN'], + 'agent-1': ACTION_MAP['MOVE_UP'], + 'agent-3': ACTION_MAP['MOVE_DOWN']}) + if self.env.agents['agent-0'].get_pos().tolist() == [2, 2]: + agent_0_percent += 1 + if self.env.agents['agent-1'].get_pos().tolist() == [2, 4]: + agent_1_percent += 1 + agent_0_success = agent_0_percent / num_trials + agent_1_success = agent_1_percent / num_trials + within_bounds_0 = (.4 < agent_0_success) and (agent_0_success < .6) + within_bounds_1 = (.4 < agent_1_success) and (agent_1_success < .6) + self.assertTrue(within_bounds_0) + self.assertTrue(within_bounds_1) + + # agent 3 wants to move into space [3,2] as does agent-2 + # however, agent-1 wants to move into [3,3] so technically + # no move is possible and no agent should move + self.move_agent('agent-0', [3, 2]) + self.move_agent('agent-2', [2, 2]) + self.move_agent('agent-1', [2, 3]) + self.move_agent('agent-3', [3, 3]) + self.env.step({'agent-0': ACTION_MAP['MOVE_UP'], + 'agent-1': ACTION_MAP['MOVE_DOWN'], + 'agent-2': ACTION_MAP['MOVE_DOWN'], + 'agent-3': ACTION_MAP['MOVE_LEFT']}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', 'P', 'P', ' ', '@'], + ['@', ' ', 'P', 'P', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + # agent 3 wants to move into space [3,2] as does agent-2 + # agent-0 will move out of the way so one of them should successfully + # get the cell + self.move_agent('agent-0', [3, 2]) + self.move_agent('agent-2', [2, 2]) + # move this agent out of the way + self.move_agent('agent-1', [4, 4]) + self.move_agent('agent-3', [3, 3]) + agent_2_success = 0 + for i in range(100): + self.env.step({'agent-0': ACTION_MAP['MOVE_DOWN'], + 'agent-2': ACTION_MAP['MOVE_DOWN'], + 'agent-3': ACTION_MAP['MOVE_LEFT']}) + if self.env.agents['agent-2'].get_pos().tolist() == [3, 2]: + agent_2_success += 1 + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', 'P', 'P', ' ', '@'], + ['@', ' ', 'P', ' ', 'P', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + else: + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', 'P', ' ', ' ', '@'], + ['@', ' ', 'P', ' ', ' ', '@'], + ['@', ' ', 'P', ' ', 'P', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + self.move_agent('agent-0', [3, 2]) + self.move_agent('agent-2', [2, 2]) + # move this agent out of the way + self.move_agent('agent-1', [4, 4]) + self.move_agent('agent-3', [3, 3]) + success_percent = agent_2_success / 100.0 + within_bounds = (.4 < success_percent) and (success_percent < .6) + self.assertTrue(within_bounds) + + # a counterclockwise rotation of a square of agents should work + # properly + self.move_agent('agent-0', [3, 2]) + self.move_agent('agent-2', [2, 2]) + # move this agent out of the way + self.move_agent('agent-1', [2, 3]) + self.move_agent('agent-3', [3, 3]) + self.env.step({'agent-0': ACTION_MAP['MOVE_UP'], + 'agent-1': ACTION_MAP['MOVE_DOWN'], + 'agent-2': ACTION_MAP['MOVE_RIGHT'], + 'agent-3': ACTION_MAP['MOVE_LEFT']}) + self.assertTrue(self.env.agents['agent-0'].get_pos().tolist() == [2, 2]) + self.assertTrue(self.env.agents['agent-1'].get_pos().tolist() == [3, 3]) + self.assertTrue(self.env.agents['agent-2'].get_pos().tolist() == [2, 3]) + self.assertTrue(self.env.agents['agent-3'].get_pos().tolist() == [3, 2]) + + # do a check that the conflict resolution still works right + # if one of the agents is trying to walk through a wall + self.move_agent('agent-0', [2, 1]) + self.move_agent('agent-1', [1, 1]) + # move these agent out of the way + self.move_agent('agent-2', [4, 4]) + self.move_agent('agent-3', [3, 3]) + curr_map = self.env.test_map.copy() + self.env.step({'agent-0': ACTION_MAP['MOVE_UP'], + 'agent-1': ACTION_MAP['MOVE_LEFT']}) + np.testing.assert_array_equal(self.env.test_map, curr_map) + + def move_agent(self, agent_id, new_pos): + self.env.agents[agent_id].set_pos(new_pos) + map_with_agents = self.env.get_map_with_agents() + agent = self.env.agents[agent_id] + agent.grid = map_with_agents + # agent.grid = util.return_view(map_with_agents, agent.pos, + # agent.row_size, agent.col_size) + self.env.agents[agent_id].update_agent_pos(new_pos) + + def rotate_agent(self, agent_id, new_rot): + self.env.agents[agent_id].update_agent_rot(new_rot) + + def construct_map(self, map, agent_id, start_pos, start_orientation): + # overwrite the map for testing + self.env = DummyMapEnv(map, num_agents=0) + self.env.reset() + + # replace the agents with agents with smaller views + self.add_agent(agent_id, start_pos, start_orientation, self.env, 2) + + def add_agent(self, agent_id, start_pos, start_orientation, env, view_len): + map_with_agents = env.get_map_with_agents() + self.env.agents[agent_id] = DummyAgent(agent_id, start_pos, start_orientation, + map_with_agents, view_len, view_len) + map_with_agents = env.get_map_with_agents() + + for agent in env.agents.values(): + # Update each agent's view of the world + agent.grid = map_with_agents + # agent.grid = util.return_view(map_with_agents, agent.pos, + # agent.row_size, agent.col_size) + self.env.agent_pos.append(start_pos) + + +class TestHarvestEnv(unittest.TestCase): + + def tearDown(self): + """Remove the env""" + self.env = None + + def test_step(self): + """Just check that the step method works at all for all possible actions""" + self.env = HarvestEnv(ascii_map=MINI_HARVEST_MAP, num_agents=1) + self.env.reset() + agents = list(self.env.agents.values()) + action_dim = agents[0].action_space.n + for i in range(action_dim): + self.env.step({'agent-0': i}) + + def test_reset(self): + self.env = HarvestEnv(ascii_map=MINI_HARVEST_MAP, num_agents=0) + self.env.reset() + # check that the map is full of apples + test_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', 'A', 'A', '@'], + ['@', ' ', ' ', 'A', 'A', '@'], + ['@', ' ', ' ', 'A', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(self.env.test_map, test_map) + + def test_apple_spawn(self): + # render apples a bunch of times and check that the probabilities are within + # a bound of what you expect. This test fill fail w/ probability + self.env = HarvestEnv(MINI_HARVEST_MAP, num_agents=0) + self.env.reset() + self.env.world_map = TEST_MAP_2.copy() + + # First test, if we step 300 times, are there five apples there? + # This should fail maybe one in 1000000 times + for i in range(300): + self.env.step({}) + num_apples = self.env.count_apples(self.env.test_map) + self.assertEqual(num_apples, 5) + + # Now, if a point is temporarily obscured by a beam but an apple should spawn there + # check that the apple still spawns there + self.env = HarvestEnv(ascii_map=MINI_HARVEST_MAP, num_agents=2) + self.env.reset() + self.move_agent('agent-0', [3, 1]) + self.move_agent('agent-1', [3, 3]) + self.rotate_agent('agent-0', 'UP') + self.rotate_agent('agent-1', 'UP') + self.env.step({'agent-1': HARVEST_ACTION_MAP['FIRE']}) + self.env.update_map([[2, 1, 'A']]) + self.env.step({}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', 'A', ' ', 'A', 'A', '@'], + ['@', 'P', ' ', 'P', 'A', '@'], + ['@', ' ', ' ', 'A', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + # If an agent is temporarily obscured by a beam, and an apple attempts to spawn there + # no apple should spawn + self.env.step({'agent-1': HARVEST_ACTION_MAP['FIRE']}) + self.env.update_map([[3, 1, 'A']]) + self.env.step({}) + + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', 'A', ' ', 'A', 'A', '@'], + ['@', 'P', ' ', 'P', 'A', '@'], + ['@', ' ', ' ', 'A', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + def test_agent_actions(self): + # set up the map + agent_id = 'agent-0' + self.construct_map(TEST_MAP_1.copy(), agent_id, [2, 2], 'LEFT') + # test firing + self.move_agent(agent_id, [3, 2]) + self.env.step({agent_id: HARVEST_ACTION_MAP['FIRE']}) + agent_view = self.env.agents[agent_id].get_state() + expected_view = np.array( + [['@'] + [' '] * 4, + ['@'] + ['F'] * 2 + [' '] * 2, + ['@'] + ['F'] + ['1'] + [' '] * 2, + ['@'] + ['F'] * 2 + [' '] * 2, + ['@'] + [' '] * 4] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + # clean up the map and then check if firing looks right + self.env.step({}) + self.rotate_agent(agent_id, 'RIGHT') + self.move_agent(agent_id, [3, 2]) + self.env.step({agent_id: HARVEST_ACTION_MAP['FIRE']}) + agent_view = self.env.agents[agent_id].get_state() + expected_view = np.array( + [['@'] + [' '] * 4, + ['@'] + [' '] + ['F'] * 3, + ['@'] + [' '] + ['1'] + ['F'] * 2, + ['@'] + [' '] + ['F'] * 3, + ['@'] + [' '] * 4] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + # Check that agents walking over apples makes them go away + np.random.seed(10) + self.construct_map(MINI_HARVEST_MAP.copy(), agent_id, [3, 2], 'RIGHT') + self.env.step({agent_id: HARVEST_ACTION_MAP['MOVE_UP']}) + self.env.step({agent_id: HARVEST_ACTION_MAP['MOVE_DOWN']}) + agent_view = self.env.agents[agent_id].get_state() + expected_view = np.array( + [['@', ' ', ' ', ' ', ' '], + ['@', ' ', ' ', 'A', 'A'], + ['@', ' ', '1', ' ', 'A'], + ['@', ' ', ' ', 'A', ' '], + ['@', '@', '@', '@', '@']] + ) + np.testing.assert_array_equal(expected_view, agent_view) + + def test_agent_rewards(self): + self.env = HarvestEnv(ascii_map=MINI_HARVEST_MAP, num_agents=2) + self.env.reset() + self.move_agent('agent-0', [2, 2]) + self.move_agent('agent-1', [3, 2]) + self.rotate_agent('agent-0', 'UP') + self.rotate_agent('agent-1', 'UP') + # walk over an apple + _, rew, _, _ = self.env.step({'agent-0': HARVEST_ACTION_MAP['MOVE_RIGHT'], + 'agent-1': HARVEST_ACTION_MAP['MOVE_RIGHT']}) + self.assertTrue(rew['agent-0'] == 1) + self.assertTrue(rew['agent-1'] == 1) + # fire a beam from agent 1 to 2 + _, rew, _, _ = self.env.step({'agent-1': HARVEST_ACTION_MAP['FIRE']}) + self.assertTrue(rew['agent-0'] == -50) + self.assertTrue(rew['agent-1'] == -1) + + def test_agent_conflict(self): + '''Test that agent conflicts are correctly resolved''' + + # test that if there are two agents and two spawning points, they hit both of them + self.env = HarvestEnv(ascii_map=BASE_MAP_2, num_agents=2) + self.env.reset() + np.testing.assert_array_equal(self.env.base_map, self.env.test_map) + + # test that agents can't walk into other agents + self.move_agent('agent-0', [3, 3]) + self.move_agent('agent-1', [3, 4]) + self.rotate_agent('agent-0', 'UP') + self.rotate_agent('agent-1', 'LEFT') + + # test that if an agents firing beam hits another agent it gets covered + self.env.step({'agent-0': HARVEST_ACTION_MAP['MOVE_LEFT']}) + self.env.step({'agent-1': HARVEST_ACTION_MAP['FIRE']}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', 'F', 'F', 'F', 'F', '@'], + ['@', ' ', 'F', 'F', 'P', '@'], + ['@', 'F', 'F', 'F', 'F', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + # but by the next step, the agent is visible again + self.env.step({}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', 'P', ' ', 'P', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + # test that if two agents fire on each other than they're still there after + self.env.agents['agent-0'].update_agent_rot('RIGHT') + self.env.step({'agent-0': HARVEST_ACTION_MAP['FIRE'], + 'agent-1': HARVEST_ACTION_MAP['FIRE']}) + self.env.step({}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', 'P', ' ', 'P', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + def test_beam_conflict(self): + """Test that after the beam is fired, obscured apples and agents are returned""" + self.env = HarvestEnv(ascii_map=MINI_HARVEST_MAP, num_agents=2) + self.env.reset() + + # test that agents can't walk into other agents + self.move_agent('agent-0', [4, 2]) + self.move_agent('agent-1', [4, 4]) + self.env.agents['agent-0'].update_agent_rot('UP') + self.env.agents['agent-1'].update_agent_rot('LEFT') + # test that if an agents firing beam hits another agent it gets covered + self.env.step({'agent-1': HARVEST_ACTION_MAP['FIRE']}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', 'A', 'A', '@'], + ['@', 'F', 'F', 'F', 'F', '@'], + ['@', ' ', 'F', 'F', 'P', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + # test that by the next step it will be returned + self.env.step({}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', 'A', 'A', '@'], + ['@', ' ', ' ', 'A', 'A', '@'], + ['@', ' ', 'P', 'A', 'P', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + def test_rotation(self): + # confirms that the rotations of agent views work correctly + self.env = HarvestEnv(ascii_map=MINI_HARVEST_MAP, num_agents=2) + rot_matrix = np.array([[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]]) + rot_left = self.env.rotate_view('LEFT', rot_matrix) + rot_left_true = np.array([[[2, 2, 2], [4, 4, 4]], [[1, 1, 1], [3, 3, 3]]]) + np.testing.assert_array_equal(rot_left, rot_left_true) + + rot_matrix = np.array([[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]]) + rot_up = self.env.rotate_view('UP', rot_matrix) + rot_up_true = np.array([[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]]) + np.testing.assert_array_equal(rot_up, rot_up_true) + + rot_matrix = np.array([[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]]) + rot_down = self.env.rotate_view('DOWN', rot_matrix) + rot_down_true = np.array([[[4, 4, 4], [3, 3, 3]], [[2, 2, 2], [1, 1, 1]]]) + np.testing.assert_array_equal(rot_down, rot_down_true) + + rot_matrix = np.array([[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]]]) + rot_right = self.env.rotate_view('RIGHT', rot_matrix) + rot_right_true = np.array([[[3, 3, 3], [1, 1, 1]], [[4, 4, 4], [2, 2, 2]]]) + np.testing.assert_array_equal(rot_right, rot_right_true) + + def clear_agents(self): + self.env.agents = {} + + def add_agent(self, agent_id, start_pos, start_orientation, env, view_len): + map_with_agents = env.get_map_with_agents() + self.env.agents[agent_id] = HarvestAgent(agent_id, start_pos, start_orientation, + map_with_agents, view_len) + map_with_agents = env.get_map_with_agents() + + for agent in env.agents.values(): + # Update each agent's view of the world + agent.grid = map_with_agents + # agent.grid = util.return_view(map_with_agents, agent.pos, + # agent.row_size, agent.col_size) + self.env.agent_pos.append(start_pos) + + def move_agent(self, agent_id, new_pos): + self.env.agents[agent_id].update_agent_pos(new_pos) + map_with_agents = self.env.get_map_with_agents() + agent = self.env.agents[agent_id] + agent.grid = map_with_agents + # agent.grid = util.return_view(map_with_agents, agent.pos, + # agent.row_size, agent.col_size) + + def rotate_agent(self, agent_id, new_rot): + self.env.agents[agent_id].update_agent_rot(new_rot) + + def construct_map(self, map, agent_id, start_pos, start_orientation): + # overwrite the map for testing + self.env = HarvestEnv(map, num_agents=0) + self.env.reset() + + # replace the agents with agents with smaller views + self.add_agent(agent_id, start_pos, start_orientation, self.env, 2) + + def test_firing_range(self): + # check that the firing beam extends as far as expected + self.env = HarvestEnv(ascii_map=FIRE_RANGE_MAP, num_agents=0) + self.env.reset() + self.add_agent('agent-0', [2, 2], 'UP', self.env, 5) + self.move_agent('agent-0', [6, 6]) + # TODO(@evinitssky) should figure out a way to shrink this + self.rotate_agent('agent-0', 'UP') + self.env.step({'agent-0': HARVEST_ACTION_MAP['FIRE']}) + expected_map = np.array([['@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', 'F', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', 'F', 'F', 'F', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', 'F', 'F', 'F', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', 'F', 'F', 'F', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', 'F', 'F', 'F', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', 'F', 'P', 'F', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + self.rotate_agent('agent-0', 'DOWN') + self.env.step({'agent-0': HARVEST_ACTION_MAP['FIRE']}) + expected_map = np.array([['@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', 'F', 'P', 'F', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', 'F', 'F', 'F', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', 'F', 'F', 'F', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', 'F', 'F', 'F', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', 'F', 'F', 'F', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', 'F', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + self.rotate_agent('agent-0', 'RIGHT') + self.env.step({'agent-0': HARVEST_ACTION_MAP['FIRE']}) + expected_map = np.array([['@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', 'F', 'F', 'F', 'F', 'F', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', 'P', 'F', 'F', 'F', 'F', 'F', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', 'F', 'F', 'F', 'F', 'F', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + self.rotate_agent('agent-0', 'LEFT') + self.env.step({'agent-0': HARVEST_ACTION_MAP['FIRE']}) + expected_map = np.array([['@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', 'F', 'F', 'F', 'F', 'F', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', 'F', 'F', 'F', 'F', 'F', 'P', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', 'F', 'F', 'F', 'F', 'F', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + + +class TestCleanupEnv(unittest.TestCase): + def test_parameters(self): + self.env = CleanupEnv(num_agents=0) + self.assertEqual(self.env.potential_waste_area, 119) + + def test_reset(self): + self.env = CleanupEnv(ascii_map=MINI_CLEANUP_MAP, num_agents=0) + self.env.reset() + # check that the map has no apples + test_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', 'H', ' ', ' ', ' ', '@'], + ['@', 'R', ' ', ' ', ' ', '@'], + ['@', 'S', ' ', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(self.env.test_map, test_map) + + def test_cleanup_beam(self): + self.env = CleanupEnv(ascii_map=FIRING_CLEANUP_MAP, num_agents=2) + self.env.reset() + self.move_agent('agent-0', [3, 3]) + self.move_agent('agent-1', [4, 2]) + self.rotate_agent('agent-0', 'LEFT') + # check that cleanup beam does four things + # 1. Cleans waste cells correctly + # 2. Is blocked by the first waste cell it encounters + # 3. Obscures agents when fired, doesn't remove them when cleaned + # 4. Is blocked by agents + self.env.step({'agent-0': CLEANUP_ACTION_MAP['CLEAN']}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', 'H', 'C', 'C', ' ', '@'], + ['@', 'R', 'C', 'P', ' ', '@'], + ['@', 'H', 'C', 'C', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + np.random.seed(12) + self.env.step({}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', 'H', 'R', ' ', ' ', '@'], + ['@', 'R', 'R', 'P', ' ', '@'], + ['@', 'H', 'P', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + # check that the cleanup beam doesn't remove apples + self.env.reset() + self.move_agent('agent-0', [3, 3]) + self.move_agent('agent-1', [4, 2]) + self.env.update_map([[3, 4, 'A']]) + self.rotate_agent('agent-0', 'RIGHT') + self.env.step({'agent-0': CLEANUP_ACTION_MAP['CLEAN']}) + self.env.step({}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', 'H', 'H', ' ', ' ', '@'], + ['@', 'R', 'H', 'P', 'A', '@'], + ['@', 'H', 'P', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + # check that you can clean up waste that an agent is standing on + self.move_agent('agent-1', [2, 2]) + self.move_agent('agent-0', [1, 3]) + self.rotate_agent('agent-0', 'DOWN') + random.seed(6) + self.env.step({'agent-0': CLEANUP_ACTION_MAP['CLEAN']}) + self.assertTrue(self.env.world_map[2, 2] == 'R') + + # check that the beams add constructively, i.e. that if one beam clears + # some waste then the next agents beam is not blocked by it and can hit + # formerly blocked cells + random.seed(7) + self.move_agent('agent-1', [2, 3]) + self.move_agent('agent-0', [4, 3]) + # put some waste back where it's needed + self.env.update_map([[2, 2, 'H']]) + self.env.update_map([[3, 1, 'H']]) + self.rotate_agent('agent-0', 'LEFT') + self.rotate_agent('agent-1', 'LEFT') + self.env.step({'agent-0': CLEANUP_ACTION_MAP['CLEAN'], + 'agent-1': CLEANUP_ACTION_MAP['CLEAN']}) + self.assertTrue(self.env.world_map[3, 1] == 'R') + + def test_firing_beam(self): + self.env = CleanupEnv(ascii_map=FIRING_CLEANUP_MAP, num_agents=2) + self.env.reset() + self.move_agent('agent-0', [3, 3]) + self.move_agent('agent-1', [4, 2]) + self.rotate_agent('agent-0', 'LEFT') + + # check that firing beam does not clean anything and is not blocked + # by anything + self.env.step({'agent-0': CLEANUP_ACTION_MAP['FIRE']}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', 'F', 'F', 'F', ' ', '@'], + ['@', 'F', 'F', 'P', ' ', '@'], + ['@', 'H', 'F', 'F', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + # check that the firing beam is removed correctly after one step + # it should not remove any waste, rivers, or agents + self.env.step({}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', 'H', 'H', ' ', ' ', '@'], + ['@', 'R', 'H', 'P', ' ', '@'], + ['@', 'H', 'P', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + # check that the cleanup beam doesn't remove apples + self.env.reset() + self.move_agent('agent-0', [3, 3]) + self.move_agent('agent-1', [4, 2]) + self.env.update_map([[3, 4, 'A']]) + self.rotate_agent('agent-0', 'RIGHT') + self.env.step({'agent-0': CLEANUP_ACTION_MAP['FIRE']}) + self.env.step({}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', ' ', ' ', ' ', '@'], + ['@', 'H', 'H', ' ', ' ', '@'], + ['@', 'R', 'H', 'P', 'A', '@'], + ['@', 'H', 'P', ' ', ' ', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + def test_apple_spawn(self): + """Confirm that apples spawn correctly in cleanup""" + self.env = CleanupEnv(ascii_map=APPLE_SPAWN_MAP_CLEANUP, num_agents=2) + self.env.reset() + for i in range(500): + self.env.step({}) + expected_map = np.array([['@', '@', '@', '@', '@', '@'], + ['@', ' ', 'P', ' ', ' ', '@'], + ['@', ' ', ' ', 'A', 'A', '@'], + ['@', ' ', ' ', 'A', 'A', '@'], + ['@', ' ', ' ', 'A', 'P', '@'], + ['@', '@', '@', '@', '@', '@']]) + np.testing.assert_array_equal(expected_map, self.env.test_map) + + def test_spawn_probabilities(self): + """Test that apple and waste spawn probabilities are set correctly""" + self.env = CleanupEnv(ascii_map=CLEANUP_PROB_MAP, num_agents=2) + self.env.reset() + + # Check that the permitted waste area is correct + self.assertEqual(self.env.compute_permitted_area(), 1) + + # Check that the potential waste area is correct + self.assertEqual(self.env.potential_waste_area, 5) + + # Check that the apple spawn probability is zero if there's too much + # waste + self.assertTrue(np.isclose(self.env.current_apple_spawn_prob, 0)) + # Check that the waste spawn probability is zero if there's too much + # waste + self.assertTrue(np.isclose(self.env.current_waste_spawn_prob, 0)) + + # Check that the waste spawn probability is computed correctly + np.random.seed(1) + self.rotate_agent('agent-0', 'LEFT') + self.rotate_agent('agent-1', 'LEFT') + self.env.step({'agent-0': CLEANUP_ACTION_MAP['CLEAN'], + 'agent-1': CLEANUP_ACTION_MAP['CLEAN']}) + self.assertTrue(np.isclose(self.env.current_waste_spawn_prob, 0.5)) + + # check that the apple spawn probability is computed correctly + while True: + self.env.step({'agent-0': CLEANUP_ACTION_MAP['CLEAN'], + 'agent-1': CLEANUP_ACTION_MAP['CLEAN']}) + if self.env.compute_permitted_area() == 4: + break + self.env.compute_probabilities() + self.assertTrue(np.isclose(self.env.current_apple_spawn_prob, 0.025)) + + # test that you can spawn waste under an agent + self.move_agent('agent-0', [2, 1]) + random.seed(2) + self.env.step({}) + self.assertTrue(self.env.world_map[2, 1] == 'H') + + def test_past_bugs(self): + '''This function is used to check that previous bugs do not regress''' + pass + + def clear_agents(self): + self.env.agents = {} + + def add_agent(self, agent_id, start_pos, start_orientation, env, view_len): + map_with_agents = env.get_map_with_agents() + self.env.agents[agent_id] = CleanupAgent(agent_id, start_pos, start_orientation, + map_with_agents, view_len) + map_with_agents = env.get_map_with_agents() + + for agent in env.agents.values(): + # Update each agent's view of the world + agent.grid = map_with_agents + # agent.grid = util.return_view(map_with_agents, agent.pos, + # agent.row_size, agent.col_size) + self.env.agent_pos.append(start_pos) + + def move_agent(self, agent_id, new_pos): + self.env.agents[agent_id].update_agent_pos(new_pos) + map_with_agents = self.env.get_map_with_agents() + agent = self.env.agents[agent_id] + agent.grid = map_with_agents + # agent.grid = util.return_view(map_with_agents, agent.pos, + # agent.row_size, agent.col_size) + + def rotate_agent(self, agent_id, new_rot): + self.env.agents[agent_id].update_agent_rot(new_rot) + + def construct_map(self, map, agent_id, start_pos, start_orientation): + # overwrite the map for testing + self.env = CleanupEnv(map, num_agents=0) + self.env.reset() + + # replace the agents with agents with smaller views + self.add_agent(agent_id, start_pos, start_orientation, self.env, 2) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_rollout.py b/tests/test_rollout.py new file mode 100644 index 00000000..43f1b9ae --- /dev/null +++ b/tests/test_rollout.py @@ -0,0 +1,20 @@ +import os +import unittest + +from rollout import Controller + + +class TestRollout(unittest.TestCase): + def setUp(self): + self.controller = Controller() + + def test_rollouts(self): + path = os.path.abspath(os.path.dirname(__file__)) + self.controller.render_rollout(horizon=5, path=path) + # cleanup + if os.path.exists("trajectory.mp4"): + os.remove("trajectory.mp4") + + +if __name__ == '__main__': + unittest.main() diff --git a/utility_funcs.py b/utility_funcs.py new file mode 100644 index 00000000..10bcbd8a --- /dev/null +++ b/utility_funcs.py @@ -0,0 +1,113 @@ +import cv2 +import os +import matplotlib.pyplot as plt +import numpy as np + + +def save_img(rgb_arr, path, name): + plt.imshow(rgb_arr, interpolation='nearest') + plt.savefig(path + name) + + +def make_video_from_image_dir(vid_path, img_folder, video_name='trajectory', fps=5): + """ + Create a video from a directory of images + """ + images = [img for img in os.listdir(img_folder) if img.endswith(".png")] + images.sort() + + rgb_imgs = [] + for i, image in enumerate(images): + img = cv2.imread(os.path.join(img_folder, image)) + rgb_imgs.append(img) + + make_video_from_rgb_imgs(rgb_imgs, vid_path, video_name=video_name, fps=fps) + + +def make_video_from_rgb_imgs(rgb_arrs, vid_path, video_name='trajectory', + fps=5, format="mp4v", resize=(640, 480)): + """ + Create a video from a list of rgb arrays + """ + print("Rendering video...") + if vid_path[-1] != '/': + vid_path += '/' + video_path = vid_path + video_name + '.mp4' + + if resize is not None: + width, height = resize + else: + frame = rgb_arrs[0] + height, width, layers = frame.shape + + fourcc = cv2.VideoWriter_fourcc(*format) + video = cv2.VideoWriter(video_path, fourcc, float(fps), (width, height)) + + for i, image in enumerate(rgb_arrs): + percent_done = int((i / len(rgb_arrs)) * 100) + if percent_done % 20 == 0: + print("\t...", percent_done, "% of frames rendered") + if resize is not None: + image = cv2.resize(image, resize, interpolation=cv2.INTER_NEAREST) + video.write(image) + + video.release() + cv2.destroyAllWindows() + + +def return_view(grid, pos, row_size, col_size): + """Given a map grid, position and view window, returns correct map part + + Note, if the agent asks for a view that exceeds the map bounds, + it is padded with zeros + + Parameters + ---------- + grid: 2D array + map array containing characters representing + pos: np.ndarray + list consisting of row and column at which to search + row_size: int + how far the view should look in the row dimension + col_size: int + how far the view should look in the col dimension + + Returns + ------- + view: (np.ndarray) - a slice of the map for the agent to see + """ + x, y = pos + left_edge = x - col_size + right_edge = x + col_size + top_edge = y - row_size + bot_edge = y + row_size + pad_mat, left_pad, top_pad = pad_if_needed(left_edge, right_edge, + top_edge, bot_edge, grid) + x += left_pad + y += top_pad + view = pad_mat[x - col_size: x + col_size + 1, + y - row_size: y + row_size + 1] + return view + + +def pad_if_needed(left_edge, right_edge, top_edge, bot_edge, matrix): + # FIXME(ev) something is broken here, I think x and y are flipped + row_dim = matrix.shape[0] + col_dim = matrix.shape[1] + left_pad, right_pad, top_pad, bot_pad = 0, 0, 0, 0 + if left_edge < 0: + left_pad = abs(left_edge) + if right_edge > row_dim - 1: + right_pad = right_edge - (row_dim - 1) + if top_edge < 0: + top_pad = abs(top_edge) + if bot_edge > col_dim - 1: + bot_pad = bot_edge - (col_dim - 1) + + return pad_matrix(left_pad, right_pad, top_pad, bot_pad, matrix, 0), left_pad, top_pad + + +def pad_matrix(left_pad, right_pad, top_pad, bot_pad, matrix, const_val=1): + pad_mat = np.pad(matrix, ((left_pad, right_pad), (top_pad, bot_pad)), + 'constant', constant_values=(const_val, const_val)) + return pad_mat diff --git a/visuallizer_rllib.py b/visuallizer_rllib.py new file mode 100644 index 00000000..fe339a05 --- /dev/null +++ b/visuallizer_rllib.py @@ -0,0 +1,261 @@ +"""Defines a multi-agent controller to rollout environment episodes w/ + agent policies.""" + +import argparse +import collections +from collections import defaultdict +import json +import numpy as np +import os +import shutil +import sys + +import ray +from ray.cloudpickle import cloudpickle +from ray.rllib.agents.registry import get_agent_class +from ray.rllib.env import MultiAgentEnv +from ray.rllib.env.base_env import _DUMMY_AGENT_ID +from ray.rllib.evaluation.sample_batch import DEFAULT_POLICY_ID +from ray.rllib.models import ModelCatalog +from ray.tune.registry import register_env +# from ray.rllib.evaluation.sampler import clip_action + +from models.conv_to_fc_net import ConvToFCNet +from models.conv_to_fc_net_actions import ConvToFCNetActions +from models.conv_to_fc_net_actions_no_lstm import ConvToFCNetActions as ConvToFCNetActionsNoLSTM +from models.conv_to_fc_net_no_lstm import ConvToFCNet as ConvToFCNetNoLSTM +import utility_funcs + + +def get_rllib_config(path): + """Return the data from the specified rllib configuration file.""" + jsonfile = path + '/params.json' # params.json is the config file + jsondata = json.loads(open(jsonfile).read()) + return jsondata + + +def get_rllib_pkl(path): + """Return the data from the specified rllib configuration file.""" + pklfile = path + '/params.pkl' # params.json is the config file + with open(pklfile, 'rb') as file: + pkldata = cloudpickle.load(file) + return pkldata + +class DefaultMapping(collections.defaultdict): + """default_factory now takes as an argument the missing key.""" + + def __missing__(self, key): + self[key] = value = self.default_factory(key) + return value + + +def default_policy_agent_mapping(unused_agent_id): + return DEFAULT_POLICY_ID + + + +def visualizer_rllib(args): + result_dir = args.result_dir if args.result_dir[-1] != '/' \ + else args.result_dir[:-1] + + config = get_rllib_config(result_dir) + pkl = get_rllib_pkl(result_dir) + + # check if we have a multiagent scenario but in a + # backwards compatible way + if config.get('multiagent', {}).get('policy_graphs', {}): + multiagent = True + config['multiagent'] = pkl['multiagent'] + else: + multiagent = False + + # Create and register a gym+rllib env + env_creator = pkl['env_config']['func_create'] + env_name = config['env_config']['env_name'] + register_env(env_name, env_creator.func) + + ModelCatalog.register_custom_model("conv_to_fc_net", ConvToFCNet) + # ModelCatalog.register_custom_model("conv_to_fc_net_no_lstm", ConvToFCNetNoLSTM) + ModelCatalog.register_custom_model("conv_to_fc_net_actions", ConvToFCNetActions) + # ModelCatalog.register_custom_model("conv_to_fc_net_actions_no_lstm", ConvToFCNetActionsNoLSTM) + + # Determine agent and checkpoint + config_run = config['env_config']['run'] if 'run' in config['env_config'] \ + else None + if (args.run and config_run): + if (args.run != config_run): + print('visualizer_rllib.py: error: run argument ' + + '\'{}\' passed in '.format(args.run) + + 'differs from the one stored in params.json ' + + '\'{}\''.format(config_run)) + sys.exit(1) + if (args.run): + agent_cls = get_agent_class(args.run) + elif (config_run): + agent_cls = get_agent_class(config_run) + else: + print('visualizer_rllib.py: error: could not find parameter ' + '\'run\' in params.json, ' + 'add argument --run to provide the algorithm or model used ' + 'to train the results\n e.g. ' + 'python ./visualizer_rllib.py /tmp/ray/result_dir 1 --run PPO') + sys.exit(1) + + # Run on only one cpu for rendering purposes if possible; A3C requires two + if config_run == 'A3C': + config['num_workers'] = 1 + else: + config['num_workers'] = 0 + + # create the agent that will be used to compute the actions + agent = agent_cls(env=env_name, config=config) + checkpoint = result_dir + '/checkpoint_' + args.checkpoint_num + checkpoint = checkpoint + '/checkpoint-' + args.checkpoint_num + print('Loading checkpoint', checkpoint) + agent.restore(checkpoint) + + policy_agent_mapping = default_policy_agent_mapping + + if hasattr(agent, "local_evaluator"): + env = agent.local_evaluator.env + multiagent = isinstance(env, MultiAgentEnv) + if agent.local_evaluator.multiagent: + policy_agent_mapping = agent.config["multiagent"][ + "policy_mapping_fn"] + + policy_map = agent.local_evaluator.policy_map + state_init = {p: m.get_initial_state() for p, m in policy_map.items()} + use_lstm = {p: len(s) > 0 for p, s in state_init.items()} + action_init = { + p: m.action_space.sample() + for p, m in policy_map.items() + } + else: + env = env_creator() + multiagent = False + use_lstm = {DEFAULT_POLICY_ID: False} + + if args.save_video: + shape = env.base_map.shape + full_obs = [np.zeros((shape[0], shape[1], 3), dtype=np.uint8) + for i in range(config["horizon"])] + + steps = 0 + while steps < (config['horizon'] or steps + 1): + mapping_cache = {} # in case policy_agent_mapping is stochastic + obs = env.reset() + agent_states = DefaultMapping( + lambda agent_id: state_init[mapping_cache[agent_id]]) + prev_actions = DefaultMapping( + lambda agent_id: action_init[mapping_cache[agent_id]]) + prev_rewards = collections.defaultdict(lambda: 0.) + done = False + last_actions = [0] * len(obs.keys()) # Number of agents + reward_total = 0.0 + while not done and steps < (config['horizon'] or steps + 1): + multi_obs = obs if multiagent else {_DUMMY_AGENT_ID: obs} + action_dict = {} + for agent_id, a_obs in multi_obs.items(): + if a_obs is not None: + policy_id = mapping_cache.setdefault( + agent_id, policy_agent_mapping(agent_id)) + p_use_lstm = use_lstm[policy_id] + if p_use_lstm: + a_action, p_state, _ = agent.compute_action( + a_obs, + state=agent_states[agent_id], + prev_action=prev_actions[agent_id], + prev_reward=prev_rewards[agent_id], + policy_id=policy_id, + info={'all_agents_actions': last_actions}) + agent_states[agent_id] = p_state + else: + a_action = agent.compute_action( + a_obs, + prev_action=prev_actions[agent_id], + prev_reward=prev_rewards[agent_id], + policy_id=policy_id, + info={'all_agents_actions': last_actions}) + action_dict[agent_id] = a_action + prev_actions[agent_id] = a_action + agent_ids = sorted(obs.keys()) + last_actions = [action_dict[a] for a in agent_ids] + + action = action_dict + + action = action if multiagent else action[_DUMMY_AGENT_ID] + next_obs, reward, done, _ = env.step(action) + if multiagent: + for agent_id, r in reward.items(): + prev_rewards[agent_id] = r + else: + prev_rewards[_DUMMY_AGENT_ID] = reward + + if multiagent: + done = done["__all__"] + reward_total += sum(reward.values()) + else: + reward_total += reward + + if args.save_video: + rgb_arr = env.map_to_colors() + full_obs[steps] = rgb_arr.astype(np.uint8) + steps += 1 + obs = next_obs + print("Episode reward", reward_total) + + if args.save_video: + path = os.path.abspath(os.path.dirname(__file__)) + '/videos' + if not os.path.exists(path): + os.makedirs(path) + images_path = path + '/images/' + if not os.path.exists(images_path): + os.makedirs(images_path) + utility_funcs.make_video_from_rgb_imgs(full_obs, path) + + # Clean up images + shutil.rmtree(images_path) + + +def create_parser(): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description='Evaluates a reinforcement learning agent ' + 'given a checkpoint.') + + # required input parameters + parser.add_argument( + 'result_dir', type=str, help='Directory containing results') + parser.add_argument('checkpoint_num', type=str, help='Checkpoint number.') + + # optional input parameters + parser.add_argument( + '--run', + type=str, + help='The algorithm or model to train. This may refer to ' + 'the name of a built-on algorithm (e.g. RLLib\'s DQN ' + 'or PPO), or a user-defined trainable function or ' + 'class registered in the tune registry. ' + 'Required for results trained with flow-0.2.0 and before.') + # optional input parameters + parser.add_argument( + '--num-rollouts', + type=int, + default=1, + help='The number of rollouts to visualize.') + parser.add_argument( + '--save-video', + action='store_true', + help='whether to save a movie or not') + parser.add_argument( + '--render', + action='store_true', + help='whether to watch the rollout while it happens') + return parser + + +if __name__ == '__main__': + parser = create_parser() + args = parser.parse_args() + ray.init(num_cpus=2) + visualizer_rllib(args)