diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index aa9b69c513..ebd636acab 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -18,7 +18,8 @@ jobs: ruby-version: '2.6' - uses: actions/setup-dotnet@v1 with: - dotnet-version: '3.1.x' + dotnet-version: '6.0.x' + include-prerelease: true - uses: pre-commit/action@v2.0.0 markdown-link-check: diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index ec43218bec..20de69073f 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -55,13 +55,14 @@ jobs: python -m pip install --progress-bar=off -e ./ml-agents -c ${{ matrix.pip_constraints }} python -m pip install --progress-bar=off -r test_requirements.txt -c ${{ matrix.pip_constraints }} python -m pip install --progress-bar=off -e ./gym-unity -c ${{ matrix.pip_constraints }} + python -m pip install --progress-bar=off -e ./pettingzoo-unity -c ${{ matrix.pip_constraints }} python -m pip install --progress-bar=off -e ./ml-agents-plugin-examples -c ${{ matrix.pip_constraints }} - name: Save python dependencies run: | pip freeze > pip_versions-${{ matrix.python-version }}.txt cat pip_versions-${{ matrix.python-version }}.txt - name: Run pytest - run: pytest --cov=ml-agents --cov=ml-agents-envs --cov=gym-unity --cov-report html --junitxml=junit/test-results-${{ matrix.python-version }}.xml -p no:warnings -v + run: pytest --cov=ml-agents --cov=ml-agents-envs --cov=gym-unity --cov=pettingzoo-unity --cov-report=html --junitxml=junit/test-results-${{ matrix.python-version }}.xml -p no:warnings -v - name: Upload pytest test results uses: actions/upload-artifact@v2 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8bce348ede..4baf0f7802 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -84,9 +84,10 @@ repos: exclude: ".*localized.*" - repo: https://github.com/dotnet/format - rev: "7e343070a0355c86f72bdee226b5e19ffcbac931" # TODO - update to a tagged version when one that includes the hook is ready. + rev: v5.1.225507 hooks: - id: dotnet-format + entry: dotnet-format whitespace args: [--folder, --include] # "Local" hooks, see https://pre-commit.com/#repository-local-hooks diff --git a/com.unity.ml-agents/CHANGELOG.md b/com.unity.ml-agents/CHANGELOG.md index fd74b8d4e8..d303aad886 100755 --- a/com.unity.ml-agents/CHANGELOG.md +++ b/com.unity.ml-agents/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to terminated teammates. (#5441) - Fixed wrong attribute name in argparser for torch device option (#5433)(#5467) - Fixed conflicting CLI and yaml options regarding resume & initialize_from (#5495) +- Added minimal analytics collection to LL-API (#5511) ## [2.1.0-exp.1] - 2021-06-09 ### Minor Changes #### com.unity.ml-agents / com.unity.ml-agents.extensions (C#) diff --git a/gym-unity/gym_unity/envs/__init__.py b/gym-unity/gym_unity/envs/__init__.py index 322d83659b..263d5634b9 100644 --- a/gym-unity/gym_unity/envs/__init__.py +++ b/gym-unity/gym_unity/envs/__init__.py @@ -19,7 +19,6 @@ class UnityGymException(error.Error): logger = logging_util.get_logger(__name__) -logging_util.set_log_level(logging_util.INFO) GymStepResult = Tuple[np.ndarray, float, bool, Dict] diff --git a/ml-agents-envs/mlagents_envs/environment.py b/ml-agents-envs/mlagents_envs/environment.py index 88dc254b8f..776c5d1030 100644 --- a/ml-agents-envs/mlagents_envs/environment.py +++ b/ml-agents-envs/mlagents_envs/environment.py @@ -10,6 +10,7 @@ from mlagents_envs.logging_util import get_logger from mlagents_envs.side_channel.side_channel import SideChannel +from mlagents_envs.side_channel import DefaultTrainingAnalyticsSideChannel from mlagents_envs.side_channel.side_channel_manager import SideChannelManager from mlagents_envs import env_utils @@ -186,6 +187,16 @@ def __init__( self._timeout_wait: int = timeout_wait self._communicator = self._get_communicator(worker_id, base_port, timeout_wait) self._worker_id = worker_id + if side_channels is None: + side_channels = [] + default_training_side_channel: Optional[ + DefaultTrainingAnalyticsSideChannel + ] = None + if DefaultTrainingAnalyticsSideChannel.CHANNEL_ID not in [ + _.channel_id for _ in side_channels + ]: + default_training_side_channel = DefaultTrainingAnalyticsSideChannel() + side_channels.append(default_training_side_channel) self._side_channel_manager = SideChannelManager(side_channels) self._log_folder = log_folder self.academy_capabilities: UnityRLCapabilitiesProto = None # type: ignore @@ -246,6 +257,8 @@ def __init__( self._is_first_message = True self._update_behavior_specs(aca_output) self.academy_capabilities = aca_params.capabilities + if default_training_side_channel is not None: + default_training_side_channel.environment_initialized() @staticmethod def _get_communicator(worker_id, base_port, timeout_wait): diff --git a/ml-agents-envs/mlagents_envs/side_channel/__init__.py b/ml-agents-envs/mlagents_envs/side_channel/__init__.py index d6ccfdf573..c9a1f5f0f7 100644 --- a/ml-agents-envs/mlagents_envs/side_channel/__init__.py +++ b/ml-agents-envs/mlagents_envs/side_channel/__init__.py @@ -2,3 +2,6 @@ from mlagents_envs.side_channel.outgoing_message import OutgoingMessage # noqa from mlagents_envs.side_channel.side_channel import SideChannel # noqa +from mlagents_envs.side_channel.default_training_analytics_side_channel import ( # noqa + DefaultTrainingAnalyticsSideChannel, # noqa +) # noqa diff --git a/ml-agents-envs/mlagents_envs/side_channel/default_training_analytics_side_channel.py b/ml-agents-envs/mlagents_envs/side_channel/default_training_analytics_side_channel.py new file mode 100644 index 0000000000..a53e686709 --- /dev/null +++ b/ml-agents-envs/mlagents_envs/side_channel/default_training_analytics_side_channel.py @@ -0,0 +1,49 @@ +import sys +import uuid +import mlagents_envs + +from mlagents_envs.exception import UnityCommunicationException +from mlagents_envs.side_channel import SideChannel, IncomingMessage, OutgoingMessage +from mlagents_envs.communicator_objects.training_analytics_pb2 import ( + TrainingEnvironmentInitialized, +) +from google.protobuf.any_pb2 import Any + + +class DefaultTrainingAnalyticsSideChannel(SideChannel): + """ + Side channel that sends information about the training to the Unity environment so it can be logged. + """ + + CHANNEL_ID = uuid.UUID("b664a4a9-d86f-5a5f-95cb-e8353a7e8356") + + def __init__(self) -> None: + # >>> uuid.uuid5(uuid.NAMESPACE_URL, "com.unity.ml-agents/TrainingAnalyticsSideChannel") + # UUID('b664a4a9-d86f-5a5f-95cb-e8353a7e8356') + # We purposefully use the SAME side channel as the TrainingAnalyticsSideChannel + + super().__init__(DefaultTrainingAnalyticsSideChannel.CHANNEL_ID) + + def on_message_received(self, msg: IncomingMessage) -> None: + raise UnityCommunicationException( + "The DefaultTrainingAnalyticsSideChannel received a message from Unity, " + + "this should not have happened." + ) + + def environment_initialized(self) -> None: + # Tuple of (major, minor, patch) + vi = sys.version_info + + msg = TrainingEnvironmentInitialized( + python_version=f"{vi[0]}.{vi[1]}.{vi[2]}", + mlagents_version="Custom", + mlagents_envs_version=mlagents_envs.__version__, + torch_version="Unknown", + torch_device_type="Unknown", + ) + any_message = Any() + any_message.Pack(msg) + + env_init_msg = OutgoingMessage() + env_init_msg.set_raw_bytes(any_message.SerializeToString()) # type: ignore + super().queue_message_to_send(env_init_msg) diff --git a/ml-agents/mlagents/training_analytics_side_channel.py b/ml-agents/mlagents/training_analytics_side_channel.py index f964f13fac..4b1f1c2dd5 100644 --- a/ml-agents/mlagents/training_analytics_side_channel.py +++ b/ml-agents/mlagents/training_analytics_side_channel.py @@ -1,12 +1,15 @@ import sys from typing import Optional -import uuid import mlagents_envs import mlagents.trainers from mlagents import torch_utils from mlagents.trainers.settings import RewardSignalType from mlagents_envs.exception import UnityCommunicationException -from mlagents_envs.side_channel import SideChannel, IncomingMessage, OutgoingMessage +from mlagents_envs.side_channel import ( + IncomingMessage, + OutgoingMessage, + DefaultTrainingAnalyticsSideChannel, +) from mlagents_envs.communicator_objects.training_analytics_pb2 import ( TrainingEnvironmentInitialized, TrainingBehaviorInitialized, @@ -16,7 +19,7 @@ from mlagents.trainers.settings import TrainerSettings, RunOptions -class TrainingAnalyticsSideChannel(SideChannel): +class TrainingAnalyticsSideChannel(DefaultTrainingAnalyticsSideChannel): """ Side channel that sends information about the training to the Unity environment so it can be logged. """ @@ -24,13 +27,14 @@ class TrainingAnalyticsSideChannel(SideChannel): def __init__(self) -> None: # >>> uuid.uuid5(uuid.NAMESPACE_URL, "com.unity.ml-agents/TrainingAnalyticsSideChannel") # UUID('b664a4a9-d86f-5a5f-95cb-e8353a7e8356') - super().__init__(uuid.UUID("b664a4a9-d86f-5a5f-95cb-e8353a7e8356")) + # Use the same uuid as the parent side channel + super().__init__() self.run_options: Optional[RunOptions] = None def on_message_received(self, msg: IncomingMessage) -> None: raise UnityCommunicationException( "The TrainingAnalyticsSideChannel received a message from Unity, " - + "this should not have happened." + "this should not have happened." ) def environment_initialized(self, run_options: RunOptions) -> None: diff --git a/pettingzoo-unity/Colab_PettingZoo.ipynb b/pettingzoo-unity/Colab_PettingZoo.ipynb index 98adbd6823..9bb4386ae3 100644 --- a/pettingzoo-unity/Colab_PettingZoo.ipynb +++ b/pettingzoo-unity/Colab_PettingZoo.ipynb @@ -1,47 +1,18 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "name": "Colab-PettingZoo-Unity.ipynb", - "private_outputs": true, - "provenance": [], - "collapsed_sections": [], - "toc_visible": true - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3.7.11 64-bit ('pettingzoo': conda)", - "metadata": { - "interpreter": { - "hash": "5d8bf1ccad4050d475e9ef008ff2de5ee0b8bb440e3e2ef203ad7e8f5a9e706b" - } - } - }, - "pycharm": { - "stem_cell": { - "cell_type": "raw", - "source": [], - "metadata": { - "collapsed": false - } - } - } - }, "cells": [ { + "cell_type": "markdown", + "metadata": {}, "source": [ "# ML-Agents PettingZoo Wrapper" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "## Setup" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", @@ -125,11 +96,11 @@ ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "### Installing ml-agents" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", @@ -149,36 +120,59 @@ ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "## Run the Environment" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { - "cell_type": "code", + "cell_type": "markdown", "metadata": { - "id": "htb-p1hSNX7D" + "jp-MarkdownHeadingCollapsed": true, + "tags": [] }, "source": [ - "#@title Select Environment { display-mode: \"form\" }\n", - "env_id = \"StrikersVsGoalie\" #@param ['Basic', '3DBall', '3DBallHard', 'GridWorld', 'Hallway', 'VisualHallway', 'CrawlerDynamicTarget', 'CrawlerStaticTarget', 'Bouncer', 'SoccerTwos', 'PushBlock', 'VisualPushBlock', 'WallJump', 'Tennis', 'Reacher', 'Pyramids', 'VisualPyramids', 'Walker', 'FoodCollector', 'VisualFoodCollector', 'StrikersVsGoalie', 'WormStaticTarget', 'WormDynamicTarget']" - ], - "execution_count": null, - "outputs": [] + "List of available environments:\n", + "* Basic\n", + "* ThreeDBall\n", + "* ThreeDBallHard\n", + "* GridWorld\n", + "* Hallway\n", + "* VisualHallway\n", + "* CrawlerDynamicTarget\n", + "* CrawlerStaticTarget\n", + "* Bouncer\n", + "* SoccerTwos\n", + "* PushBlock\n", + "* VisualPushBlock\n", + "* WallJump\n", + "* Tennis\n", + "* Reacher\n", + "* Pyramids\n", + "* VisualPyramids\n", + "* Walker\n", + "* FoodCollector\n", + "* VisualFoodCollector\n", + "* StrikersVsGoalie\n", + "* WormStaticTarget\n", + "* WormDynamicTarget" + ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "### Start Environment with PettingZoo Wrapper" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "YSf-WhxbqtLw" }, + "outputs": [], "source": [ "# -----------------\n", "# This code is used to close an env that might not have been closed before\n", @@ -189,29 +183,26 @@ "# -----------------\n", "\n", "import numpy as np\n", - "from mlagents_envs.registry import default_registry\n", - "from pettingzoo_unity import UnityToPettingZooWrapper\n", - "\n", - "unity_env = default_registry[env_id].make()\n", - "env = UnityToPettingZooWrapper(unity_env)" - ], - "execution_count": null, - "outputs": [] + "from pettingzoo_unity.envs import StrikersVsGoalie # import unity environment\n", + "env = StrikersVsGoalie.env()" + ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "### Stepping the environment\n", "\n", "Example of interacting with the environment in basic RL loop. It follows the same interface as described in [PettingZoo API page](https://www.pettingzoo.ml/api)." - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "dhtl0mpeqxYi" }, + "outputs": [], "source": [ "num_cycles = 10\n", "\n", @@ -225,18 +216,16 @@ " else:\n", " action = env.action_spaces[agent].sample() # randomly choose an action for example\n", " env.step(action)" - ], - "execution_count": null, - "outputs": [] + ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "### Additional Environment API\n", "\n", "All the API described in the `Additional Environment API` section in the [PettingZoo API page](https://www.pettingzoo.ml/api) are all supported. A few examples are shown below." - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", @@ -281,22 +270,50 @@ ] }, { + "cell_type": "markdown", + "metadata": {}, "source": [ "### Close the Environment to free the port it is using" - ], - "cell_type": "markdown", - "metadata": {} + ] }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "a7KatdThq7OV" }, + "outputs": [], "source": [ "env.close()" - ], - "execution_count": null, - "outputs": [] + ] } - ] + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "name": "Colab-UnityEnvironment-1-Run.ipynb", + "private_outputs": true, + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 } diff --git a/pettingzoo-unity/pettingzoo_unity/__init__.py b/pettingzoo-unity/pettingzoo_unity/__init__.py index f4e59319b7..183a39d529 100644 --- a/pettingzoo-unity/pettingzoo_unity/__init__.py +++ b/pettingzoo-unity/pettingzoo_unity/__init__.py @@ -6,5 +6,6 @@ try: from .pettingzoo_wrapper import UnityToPettingZooWrapper # noqa + import pettingzoo_unity.envs # noqa except ImportError: pass diff --git a/pettingzoo-unity/pettingzoo_unity/envs/__init__.py b/pettingzoo-unity/pettingzoo_unity/envs/__init__.py new file mode 100644 index 0000000000..697b7e3cdc --- /dev/null +++ b/pettingzoo-unity/pettingzoo_unity/envs/__init__.py @@ -0,0 +1,61 @@ +from mlagents_envs.registry import default_registry +from pettingzoo_unity import UnityToPettingZooWrapper +from typing import Optional +from mlagents_envs.exception import UnityWorkerInUseException +from mlagents_envs.side_channel.environment_parameters_channel import ( + EnvironmentParametersChannel, +) +from mlagents_envs.side_channel.engine_configuration_channel import ( + EngineConfigurationChannel, +) +from mlagents_envs.side_channel.stats_side_channel import StatsSideChannel +from mlagents_envs import logging_util + +logger = logging_util.get_logger(__name__) + + +class PettingZooEnv: + def __init__(self, env_id: str) -> None: + self.env_id = env_id + + def env(self, seed: Optional[int] = None, **kwargs) -> UnityToPettingZooWrapper: + """ + Creates the environment with env_id from unity's default_registry and wraps it in a UnityToPettingZooWrapper + :param seed: The seed for the action spaces of the agents. + :param kwargs: Any argument accepted by `UnityEnvironment`class except file_name + """ + # If not side_channels specified, add the followings + if "side_channels" not in kwargs: + kwargs["side_channels"] = [ + EngineConfigurationChannel(), + EnvironmentParametersChannel(), + StatsSideChannel(), + ] + _env = None + # If no base port argument is provided, try ports starting at 6000 until one is free + if "base_port" not in kwargs: + port = 6000 + while _env is None: + try: + kwargs["base_port"] = port + _env = default_registry[self.env_id].make(**kwargs) + except UnityWorkerInUseException: + port += 1 + pass + else: + _env = default_registry[self.env_id].make(**kwargs) + return UnityToPettingZooWrapper(_env, seed) + + +# Register each environment in default_registry as a PettingZooEnv +for key in default_registry: + env_name = key + if key[0].isdigit(): + env_name = key.replace("3", "Three") + if not env_name.isidentifier(): + logger.warning( + f"Environment id {env_name} can not be registered since it is" + f"not a valid identifier name." + ) + continue + locals()[env_name] = PettingZooEnv(key) diff --git a/pettingzoo-unity/pettingzoo_unity/tests/__init__.py b/pettingzoo-unity/pettingzoo_unity/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2