Skip to content

[WIP] Unity Environment Registry #3967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
May 20, 2020
Merged

[WIP] Unity Environment Registry #3967

merged 16 commits into from
May 20, 2020

Conversation

vincentpierre
Copy link
Contributor

@vincentpierre vincentpierre commented May 14, 2020

JIRA ticket
Design Document

In This PR : Prototype of the Unity Environment Registry
Uploaded the 3DBall and Basic Environments for mac only

How to use on Python :

from mlagents_envs.registry import default_registry

print(default_registry["3DBall"].description)
env = default_registry["3DBall"].make()
env.reset()

for i in range(10):
   print(i)
   env.step()

env.close()

Types of change(s)

  • Bug fix
  • New feature
  • Code refactor
  • Breaking change
  • Documentation update
  • Other (please describe)

Checklist

  • Added tests that prove my fix is effective or that my feature works
  • Updated the changelog (if applicable)
  • Updated the documentation (if applicable)
  • Updated the migration guide (if applicable)

[JIRA ticket](https://jira.unity3d.com/browse/MLA-997)
[Design Document](https://docs.google.com/document/d/1bFQ3_oXsA80FMou8kwqYxC53kqG5L3i0mbTQUH4shY4/edit#)

In This PR : Prototype of the Unity Environment Registry
Uploaded the 3DBall and Basic Environments for mac only

How to use on Python :
```python
from mlagents_envs.registry import UnityEnvRegistry

registry = UnityEnvRegistry()

print(registry["3DBall"].description)
env = registry["3DBall"].make()
env.reset()

for i in range(10):
   print(i)
   env.step()

env.close()
```
@vincentpierre vincentpierre self-assigned this May 14, 2020
@vincentpierre vincentpierre requested review from andrewcoh, chriselion, mmattar and anupam-142857 and removed request for mmattar May 14, 2020 21:54
platform dependent : It will only return a Unity executable compatible with the
computer's OS. If no executable is found, None will be returned.
"""
_, bin_dir = get_tmp_dir()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have the final location of these be in a user-overridable directory, not a temp dir (since ideally they won't be temp).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think tmp dir is fine for now. This is the way ai2-thor does it for example.

@chriselion
Copy link
Contributor

Here's a sketch of what I had in mind for the remote yaml files:

class UnityEnvRegistry(Mapping):
    def __init__(manifest_loc_or_url: str)
        self._manifest_loc_or_url = manifest_loc_or_url
        self._synced = False

    def _populate_from_manifest():
        if self._synced:
            return

        if is_remote_url(self._manifest_loc_or_url):
            manifest = download(self._manifest_loc_or_url)
        else:
            manifest = yaml_load(self._manifest_loc_or_url)

        for env in manifest["environments"]:
            self.register(RemoteRegistryEntry.from_manifest_entry(env))
        self._synced = True

    def clear():
        self._synced = False # Sync next time we do something
        # clear internal state

    def __getitem__(self, identifier: str) -> BaseRegistryEntry:
        self._populate_from_manifest()
        # do work
    
    def __len__(self) -> int:
        self._populate_from_manifest()
        # do work
    
    def __iter__(self) -> Iterator[Any]:
        self._populate_from_manifest()
        # do work

 - UnityEnvRegistry is no longer static and needs to be instantiated
 - Providing a default_registry that will contains our environments
 - Added a functionality to register RemoteRegistryEntry with a yaml file
@vincentpierre
Copy link
Contributor Author

Made some updates :

  • UnityEnvRegistry is no longer static and needs to be instantiated
  • Providing a default_registry instance that will contains our environments
  • Added a functionality to register RemoteRegistryEntry with a yaml file

yaml file looks like :

environments:
  - 3DBall:
     expected_reward: 100
     description: | ## 3DBall: 3D Balance Ball
      ...
     linux_url: null
     darwin_url: https://storage.googleapis.com/mlagents-test-environments/1.0.0/darwin/3DBall.zip
     win_url: null

  - Basic:
     expected_reward: 0.93
     description: | ## Basic
       ...
     linux_url: null
     darwin_url: https://storage.googleapis.com/mlagents-test-environments/1.0.0/darwin/Basic.zip
     win_url: null

@@ -51,6 +51,7 @@ def run(self):
"numpy>=1.14.1,<2.0",
"Pillow>=4.2.1",
"protobuf>=3.6",
"pyyaml",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not too happy about this...

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not specify a lower bound for pyyaml version ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this from the mlagents setup.py file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copying from mlagents setup.py doesn't make it correct. I suggest having a lower bound on the pyyaml version in both places.

…he url in its name to make sure the right environment is opened and not just the one present. Useful if environments are renamed
print("")

# Clean up zip
print_progress(f" Cleaning up {name}", 0)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use a logger instead of print statements in lines 117,118,123,126

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replaced one of the calls but I want a progress bar and logger can't do that

Converts a local yaml file into a Python dictionary
"""
with open(path) as data_file:
return yaml.safe_load(data_file)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might want to handle an exception here in case yaml.safe_load throws an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is wrong with raising that error? I don't want to suppress it.

Copy link

@anupam-142857 anupam-142857 May 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firstly there is nothing wrong in what you did. Secondly, raising an exception allows you to give a more informative message to the user instead of a generic error message that comes from python. It's more of a matter of coding style rather than correctness.

It was just a suggestion to begin with :)

@@ -51,6 +51,7 @@ def run(self):
"numpy>=1.14.1,<2.0",
"Pillow>=4.2.1",
"protobuf>=3.6",
"pyyaml",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not specify a lower bound for pyyaml version ?


# Download zip
try:
request = urllib.request.urlopen(url)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please pass a timeout here, otherwise it could hang indefinitely. I'd say 30 seconds as a default, with optional parameters to override?

logger.debug(
f"Local environment {name} not found, downloading environment from {url}"
)
download_and_extract_zip(url, name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Connection errors will raise from here and aren't caught. Since these might be transient errors and not the users fault, we should catch them and return null (or even better, retry a few times).

with open(zip_file_path, "wb") as zip_file:
downloaded = 0
while True:
buffer = request.read(BLOCK_SIZE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the same vein, pretty sure this could fail sporadically; needs some better error handling.

- ...
```

Your users can now use your environment with the following code :
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be made clear that they are not pushing these builds to our storage but they must host it on their own? Is there another step to use the new env i.e. pip install foo-env in gym or is this beyond the scope of this WIP?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no additional step. No need to pip install anything, just get the url of the yaml

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if you have a new env do you submit a PR to our repo to have it added to the registry? Or do you need to go to a fork?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so it seems the documentation is not clear. Please advise on how to make more explicit.
If you made a brand new environment, you need to :

  • Compile it into an executable
  • Zip the executable
  • Upload the executable to YOUR OWN hosting service (example : gcs)
  • Create a yaml file that contains the url to the zip file
  • Upload the yaml file to hosting service

Once this is done, to share your environment, all you need to do is advertise the URL to the yaml. If a user wants your environment, all they have to do is call

env_registry = UnityEnvRegistry()
env_registry.register_from_yaml("your-url-here")

And they will have your environment.
No PR, no fork, no pip

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrew is this confusing? Should I change the documentation to give more details ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that makes sense. I was being dense. Also, thats not me^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😨

@vincentpierre vincentpierre marked this pull request as ready for review May 18, 2020 23:39
@awjuliani awjuliani mentioned this pull request May 19, 2020
10 tasks
@chriselion
Copy link
Contributor

I can come in another iteration, but I think we should have some notion of versioning of a given environment (separate from the communicator version) to indicate changes/iterations on the scene itself.

@vincentpierre vincentpierre merged commit 742c2fb into master May 20, 2020
@delete-merged-branch delete-merged-branch bot deleted the develop-env-registry branch May 20, 2020 20:30
@yijiezh
Copy link

yijiezh commented Jul 14, 2020

I tried

from mlagents_envs.registry import default_registry

print(default_registry["Basic"].description)
env = default_registry["Basic"].make()

print(env.reset())

I got None in the stdout.
Didn't the env here same as the a gym env, and that should return an observation vector? Anything I missed?

@chriselion
Copy link
Contributor

@yijiezh BaseRegistryEntry.make() returns a BaseEnv, and BaseEnv.reset() returns None, so that's expected:

@abstractmethod
def reset(self) -> None:
"""
Signals the environment that it must reset the simulation.
"""

Please create a new issue (or forum thread) if you have more problems.

@Unity-Technologies Unity-Technologies locked as resolved and limited conversation to collaborators Jul 14, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants