Skip to content
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

Major refactoring gymnasium et al #88

Merged
merged 20 commits into from
Sep 30, 2023
Merged

Conversation

alexpalms
Copy link
Member

No description provided.

- Add timer RAM state
- Remove deprecated obs visualizations
- Add VScode pytest integration
- Update engine_mock to use timer
- Add timer to wrappers options script
- Remove interface with protobuf and use its native datastructure
- Unify 1P and 2P step method
- New settings `role` and `n_players` instead of `player` refactoring
- Update and refactor episode recording and dataset loading
- Implement snake_case for keys and refactor accordingly
- Add frame compression (encoding) to episode recording
- Align tests with new refactoring for recording
- Add tests for examples and dataloader
- Add manual test for engine mock, centralize engine mocking, fixed engine mock bug
- Update obs wrappers for frame warping, fixed random tests
- Use new gymnasium interface
- Update setup.py accordingly:
  - Remove gym, add gymnasium
  - Remove setuptools pin
  - Update version for diambra-engine
- Unify observation wrapper frame stack including dilation
- Manage random values directly from python
- Manage change of options at restart
- Refactor tests and examples accordingly
@alexpalms alexpalms force-pushed the major-refactoring-gymnasium-et-al branch from 6ad98da to b182ccc Compare September 12, 2023 03:06
setup.py Outdated Show resolved Hide resolved
Copy link
Member

@discordianfish discordianfish left a comment

Choose a reason for hiding this comment

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

Very nice, I think this is all in all a big improvement also code quality wise!
One general thing: Dunno how well that is supported with python, but making a interface for the wrapper so you can only pass in classes that have the required methods (e.g step etc) would make this even more robust. This is what chatgpt suggests:

In Python, you can achieve this using abstract base classes (ABCs) provided by the abc module. The idea is to create an abstract base class that defines the step method as an abstract method. Then, any concrete class that inherits from this abstract base class must provide an implementation of the step method.

Here's how you can do it:

  1. Define the abstract base class:
from abc import ABC, abstractmethod

class StepInterface(ABC):

    @abstractmethod
    def step(self):
        pass
  1. Create concrete classes that implement the step method:
class ClassA(StepInterface):
    def step(self):
        print("ClassA step")

class ClassB(StepInterface):
    def step(self):
        print("ClassB step")

If you try to create an instance of a class that inherits from StepInterface but doesn't implement the step method, you'll get a TypeError.

  1. You can then create a list of these class instances and ensure they all have the step method:
objects = [ClassA(), ClassB()]

for obj in objects:
    obj.step()

This will ensure that all classes in the list have a step method. If a class doesn't implement the method, you'll find out when you try to create an instance of the class (not when you try to call the method), which can be a helpful way to catch errors early.

@alexpalms
Copy link
Member Author

Very nice, I think this is all in all a big improvement also code quality wise! One general thing: Dunno how well that is supported with python, but making a interface for the wrapper so you can only pass in classes that have the required methods (e.g step etc) would make this even more robust. This is what chatgpt suggests:

In Python, you can achieve this using abstract base classes (ABCs) provided by the abc module. The idea is to create an abstract base class that defines the step method as an abstract method. Then, any concrete class that inherits from this abstract base class must provide an implementation of the step method.

Here's how you can do it:

  1. Define the abstract base class:
from abc import ABC, abstractmethod

class StepInterface(ABC):

    @abstractmethod
    def step(self):
        pass
  1. Create concrete classes that implement the step method:
class ClassA(StepInterface):
    def step(self):
        print("ClassA step")

class ClassB(StepInterface):
    def step(self):
        print("ClassB step")

If you try to create an instance of a class that inherits from StepInterface but doesn't implement the step method, you'll get a TypeError.

  1. You can then create a list of these class instances and ensure they all have the step method:
objects = [ClassA(), ClassB()]

for obj in objects:
    obj.step()

This will ensure that all classes in the list have a step method. If a class doesn't implement the method, you'll find out when you try to create an instance of the class (not when you try to call the method), which can be a helpful way to catch errors early.

This suggestion is regarding the list of wrappers class to be provided as wrapper setting? If so, I see your point and it is interesting. There is a subtle thing though, in particular you can have different types of wrappers classes you can apply, like Observation Wrappers, Reward Wrappers or the more generic Gym Wrapper. Not all of them have the same method (e.g. the obs wrapper does not have any method of those at all, but only the observation method).

What I can do though, is to add a sanity check and make sure that all the classes in the list provided by the user are one of the gym wrappers class, what do you thing?

@discordianfish
Copy link
Member

This suggestion is regarding the list of wrappers class to be provided as wrapper setting? If so, I see your point and it is interesting. There is a subtle thing though, in particular you can have different types of wrappers classes you can apply, like Observation Wrappers, Reward Wrappers or the more generic Gym Wrapper. Not all of them have the same method (e.g. the obs wrapper does not have any method of those at all, but only the observation method).

Now I'm thinking more about this, an wrapper basically wraps the whole env, so it takes an env and returns a new env, right? So we could possibly type additional_wrappers_list with gym.Env or gym.Wrapper.

That being said, now I'm wondering why we have additional_wrappers_list at all. Why not create a diambra env and then add a wrapper like this:

env = arena.make("doapp"...)
wrapped = Wrapper(env, ...)

@alexpalms
Copy link
Member Author

This suggestion is regarding the list of wrappers class to be provided as wrapper setting? If so, I see your point and it is interesting. There is a subtle thing though, in particular you can have different types of wrappers classes you can apply, like Observation Wrappers, Reward Wrappers or the more generic Gym Wrapper. Not all of them have the same method (e.g. the obs wrapper does not have any method of those at all, but only the observation method).

Now I'm thinking more about this, an wrapper basically wraps the whole env, so it takes an env and returns a new env, right? So we could possibly type additional_wrappers_list with gym.Env or gym.Wrapper.

That being said, now I'm wondering why we have additional_wrappers_list at all. Why not create a diambra env and then add a wrapper like this:

env = arena.make("doapp"...)
wrapped = Wrapper(env, ...)

I follow you and I see your points and I understand your question.

In fact, it is possible to do exactly what you suggest in your code, applying wrappers after the environment has been created.

The use of the wrappers argument (previously named additional_wrappers_list) is key when you want to use precooked make functions for external Libs, like stable baselines 2 and 3 (make_sb3_env), as well as others we will interface.

See as an example this:

env = diambra.arena.make(game_id, env_settings, wrappers_settings,

Since SB wraps the env in their own specific wrappers/classes, we want to have a way to insert custom wrappers in addition to the standard ones we provide after we apply ours (i.e. after our make call, line 41 in the link) but before RL lib specific ones are applied (i.e. line 48 in the link)

It is true that you can build the wrapping sequence for SB on your own, and thus use the wrapping sequence you prefer, but that is considered an advanced use case.
The typical use cases already showed up in the discord support channel, highlighting the importance of having a pre-built interface with SB (and other libs) as well as being able to add wrappers before the lib-specific ones:

Let me know your thoughts!

…se keys for ram states, rename wrappers list key
- Acquire engine RAM states refactoring and PB enum usage
- Acquire Role as Enum modification
- Rework base observation space keeping P1 and P2 as in engine
- Use wrapper to add action to obs space
- Keep discrete and multi discrete action type in observation space
- Optimize actions related wrappers
- Add role-relative obs wrapper
@alexpalms alexpalms force-pushed the major-refactoring-gymnasium-et-al branch from c3829da to 4a3b27a Compare September 21, 2023 04:43
@alexpalms alexpalms force-pushed the major-refactoring-gymnasium-et-al branch from 32120db to b1e24d2 Compare September 22, 2023 19:22
@alexpalms alexpalms merged commit 06270b5 into main Sep 30, 2023
30 checks passed
@alexpalms alexpalms deleted the major-refactoring-gymnasium-et-al branch September 30, 2023 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants