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

make env optional arg while creating from buffers #137

Merged

Conversation

avjmachine
Copy link
Contributor

@avjmachine avjmachine commented Aug 31, 2023

Description

Make the env argument as optional in the create_dataset_from_buffers() function in utils

Fixes # (issue), Depends on # (pull request)

Type of change

Please delete options that are not relevant.

  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Screenshots

Please attach before and after screenshots of the change if applicable.
To upload images to a PR -- simply drag and drop or copy paste.

Checklist:

  • I have run the pre-commit checks with pre-commit run --all-files (see CONTRIBUTING.md instructions to set it up)
  • I have run pytest -v and no errors are present.
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I solved any possible warnings that pytest -v has generated that are related to my code to the best of my knowledge.
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Copy link
Collaborator

@balisujohn balisujohn left a comment

Choose a reason for hiding this comment

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

Thanks for your work on this so far. I requested changes in two places. I also think there should be a new test which creates a dataset without providing an env argument, and makes sure that everything still works as intended. Once that is all done, I will review again.

minari/utils.py Outdated Show resolved Hide resolved
minari/utils.py Outdated Show resolved Hide resolved
raise error if obs space or action space not provided when env is none
remove unnecessary if conditions as obs & action space can't be none
@balisujohn
Copy link
Collaborator

Thanks for your work on this; it looks almost ready! I think it would be good to add a test to test_dataset_creation where a dataset is created from buffers without the env being set, and then also tries to load that dataset after it is created. Additionally, it looks like pre-commit is failing. Once those two things are done, it should be ready to merge.

minari/utils.py Outdated Show resolved Hide resolved
minari/utils.py Outdated Show resolved Hide resolved
minari/utils.py Outdated
Comment on lines 175 to 176
dataset.spec.env_spec.max_episode_steps for dataset in datasets_to_combine
dataset.spec.env_spec.max_episode_steps
for dataset in datasets_to_combine # pyright: ignore[reportGeneralTypeIssues]
Copy link
Member

Choose a reason for hiding this comment

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

Actually, since now the datasets can have a env_spec=None, we should address also this case.
You can create the list of env_spec and filter out None values

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As of now, I'm raising an error if any of the datasets to be combined have no env_spec. Please do let me know if you'd prefer some other specific action instead of this.

minari/utils.py Outdated Show resolved Hide resolved
minari/utils.py Outdated
if observation_space is None:
observation_space = env.observation_space
observation_space = (
env.observation_space # pyright: ignore[reportOptionalMemberAccess]
Copy link
Member

Choose a reason for hiding this comment

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

why do you need this? There is no way reaching here when env is None; do you got errors from the pre-commit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I got errors from pre-commit.

minari/utils.py Outdated Show resolved Hide resolved
tests/utils/test_dataset_combine.py Outdated Show resolved Hide resolved
@younik younik force-pushed the env-optional-create-ds-buffers branch from 1a61d32 to 5824961 Compare October 23, 2023 03:53
Copy link
Member

@younik younik left a comment

Choose a reason for hiding this comment

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

I had to play around a bit to understand how to solve so I just pushed the code to your branch; now should be good.
Last thing before merging, can you add a test for combining datasets without env_spec?

@avjmachine
Copy link
Contributor Author

avjmachine commented Oct 24, 2023

@younik Okay, so using assert you could handle the pyright optional member access issue! Thanks a lot for helping with that!
I'll start working on that test, but before that I have a question.

I see that the "validate_datasets_to_combine" has the check "Tests if the datasets were created with the same environment" at L184-186 in utils.py removed. Is this intentional? Don't we need to compare if all the datasets have exactly the same env_spec? Am I missing something here?

minari/utils.py Outdated
env_spec = dataset.spec.env_spec
if env_spec is not None:
assert (
common_env_spec is not None
Copy link
Member

@younik younik Oct 24, 2023

Choose a reason for hiding this comment

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

Oh my bad, you are right, thanks! Can you fix it? You cannot simply assert check env_spec == common_env_spec tho, as max_episode_steps may differ.

Would be nice to also add a test on combining with different env_spec then (an error is expected)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay, sure. I'll add the fix and the test as well.

Copy link
Contributor Author

@avjmachine avjmachine Oct 27, 2023

Choose a reason for hiding this comment

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

I've added the fix. Please verify if this is fine. Also, I wanted to know if we could use assert in non-testing code as I have read that assert statements are unsafe in production environments could be disabled in python interpreters during optimization (quoting one source here). They recommend using assert only in testing and debugging code, but I've continued to use assert here as our repo already has many asserts in non-testing code.

The test case and pyright issue is still work in progress though I've done some refactoring to make use of the dataset generation with no env in the combining dataset test cass too.

Edit: Just noticed that we need a separate for loop statement for updating the max_episode_steps after line 186. I missed that. Will correct it. But please do advise me on the assert statement.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for the fix! I left a couple of comments.
You are right about the assert, the difference is: when you believe something cannot happen you can use assert (e.g. asserts on line 401 and 404 of utils.py, as there is the check above). When it is something that can happen, but you want to let the user that it is not allowed, you should raise an error.
Unfortunately, in Minari we often use asserts as exceptions. I would be happy to merge another PR that addresses it.

Edit: Just noticed that we need a separate for loop statement for updating the max_episode_steps after line 186. I missed that. Will correct it. But please do advise me on the assert statement.

What do you mean? I don't see it.

minari/utils.py Show resolved Hide resolved
tests/common.py Outdated
Comment on lines 406 to 407
if dataset.env_spec is None:
raise ValueError("Recovering environment is not possible when env_spec is None")
Copy link
Member

Choose a reason for hiding this comment

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

this it is not necessary I believe, as recover_environment will throw an error itself if env_spec is None

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed the redundant raise error

minari/utils.py Outdated
env_spec = dataset.spec.env_spec
if env_spec is not None:
assert (
common_env_spec is not None
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for the fix! I left a couple of comments.
You are right about the assert, the difference is: when you believe something cannot happen you can use assert (e.g. asserts on line 401 and 404 of utils.py, as there is the check above). When it is something that can happen, but you want to let the user that it is not allowed, you should raise an error.
Unfortunately, in Minari we often use asserts as exceptions. I would be happy to merge another PR that addresses it.

Edit: Just noticed that we need a separate for loop statement for updating the max_episode_steps after line 186. I missed that. Will correct it. But please do advise me on the assert statement.

What do you mean? I don't see it.

@avjmachine
Copy link
Contributor Author

@younik I'm currently working on the merge conflicts with the eval_env changes that got merged yesterday. I had one question regarding this. Could there be a scenario where there is an eval_env even when env is None? I mean, can a user provide a eval_env for a dataset generated with buffers without an env?

@younik
Copy link
Member

younik commented Oct 30, 2023

@younik I'm currently working on the merge conflicts with the eval_env changes that got merged yesterday. I had one question regarding this. Could there be a scenario where there is an eval_env even when env is None? I mean, can a user provide a eval_env for a dataset generated with buffers without an env?

Thank you, good point. I don't see any common case when this would happen, but also no reason to constraint it. I would simply do what is easier in code, which is no constraint I guess.

minari/utils.py Outdated
Comment on lines 516 to 517
if isinstance(env, (str, EnvSpec, type(None))) and (observation_space is None or action_space is None):
raise ValueError("Both observation space and action space must be provided, if env is str|EnvSpec|None")
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'm assuming here that action_space and observation_space cannot be extracted unless it is of type gym.Env

Copy link
Member

Choose a reason for hiding this comment

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

Uhm, if it is str or EnvSpec, you can recover the environment with gym.make; I would do that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, if it is a str or EnvSpec, there would be 2 steps to extract the action_space and observation_space?

  1. recover env as gym.Env with gym.make;
  2. recover action_space and observation_space from this gym.Env.

Is this correct?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, exactly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

implemented the above logic

minari/utils.py Outdated
), "The datasets to be combined have different values for `env_spec` attribute."
# checking equivalence of all datasets with an env
for dataset in datasets_to_combine:
if dataset.spec.env_spec is not None:
Copy link
Member

Choose a reason for hiding this comment

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

They are required to be not None, it shouldn't be possible to combine datasets with some None env_spec and some None.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay, I think this answers my question in the previous comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

minari/utils.py Outdated
Comment on lines 196 to 201
env_spec_copy = copy.deepcopy(dataset.spec.env_spec)
env_spec_copy.max_episode_steps = common_env_spec.max_episode_steps
if env_spec_copy != common_env_spec:
raise ValueError(
"The datasets to be combined have different values for `env_spec` attribute."
)
Copy link
Member

Choose a reason for hiding this comment

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

can you just move this check in the other for loop and remove this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

minari/utils.py Show resolved Hide resolved
minari/utils.py Outdated
Comment on lines 409 to 420
if eval_env is None:
warnings.warn(
f"`eval_env` is set to None. If another environment is intended to be used for evaluation please specify corresponding Gymnasium environment (gym.Env | gym.envs.registration.EnvSpec).\
if env_spec is not None:
warnings.warn(
f"`eval_env` is set to None. If another environment is intended to be used for evaluation please specify corresponding Gymnasium environment (gym.Env | gym.envs.registration.EnvSpec).\
If None the environment used to collect the data (`env={env_spec}`) will be used for this purpose.",
UserWarning,
)
UserWarning,
)
else:
warnings.warn(
"Since both `eval_env` and `env` used to collect the data are None, no environment can be used for evaluation",
UserWarning,
)
Copy link
Member

Choose a reason for hiding this comment

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

I would simplify this by having two independent checks. One for env_spec with a warning stating if you didn't declare it, and the other for eval_env (without checking what env_spec is).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

made the 2 checks independent

minari/utils.py Outdated
eval_env (Optional[str|gym.Env|EnvSpec]): Gymnasium environment(gym.Env)/environment id(str)/environment spec(EnvSpec) to use for evaluation with the dataset. After loading the dataset, the environment can be recovered as follows: `MinariDataset.recover_environment(eval_env=True).
If None the `env` used to collect the buffer data should be used for evaluation.
If None, and if the `env` used to collect the buffer data is available, latter should be used for evaluation.
Copy link
Member

Choose a reason for hiding this comment

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

not related to your PR, but should -> will

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

raise error when some datasets have env_spec, and others don't
combine two for-loops that check equality & update episodes for common_env_spec into one
Copy link
Member

@younik younik left a comment

Choose a reason for hiding this comment

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

LGTM; thanks for implementing this!

@younik
Copy link
Member

younik commented Nov 9, 2023

Actually, can you fix pre-commit @avjmachine

Looks like you should use env_spec in line 527 & 530 in utils, as env can be just a string.
For the documentation, it is not related to your PR, so you can ignore it

@avjmachine
Copy link
Contributor Author

Actually, can you fix pre-commit @avjmachine

Looks like you should use env_spec in line 527 & 530 in utils, as env can be just a string. For the documentation, it is not related to your PR, so you can ignore it

In line 524, we are overwriting the env variable if its just a string, using "env = gym.make(env)". So, will this work or is there a risk of env not having an observation_space or action_space attribute even after using gym.make?

@younik
Copy link
Member

younik commented Nov 16, 2023

Yes, it is

Actually, can you fix pre-commit @avjmachine
Looks like you should use env_spec in line 527 & 530 in utils, as env can be just a string. For the documentation, it is not related to your PR, so you can ignore it

In line 524, we are overwriting the env variable if its just a string, using "env = gym.make(env)". So, will this work or is there a risk of env not having an observation_space or action_space attribute even after using gym.make?

No, there isn't; however, pyright is not able to infer it and throws an error.
Changing assert env is not None to assert isinstance(env, gym.Env) should work

@avjmachine
Copy link
Contributor Author

Yes, it is

Actually, can you fix pre-commit @avjmachine
Looks like you should use env_spec in line 527 & 530 in utils, as env can be just a string. For the documentation, it is not related to your PR, so you can ignore it

In line 524, we are overwriting the env variable if its just a string, using "env = gym.make(env)". So, will this work or is there a risk of env not having an observation_space or action_space attribute even after using gym.make?

No, there isn't; however, pyright is not able to infer it and throws an error. Changing assert env is not None to assert isinstance(env, gym.Env) should work

Done. I didn't get this pyright error on my local env. Hence, got confused.
Also, fixed additional pyright errors which I got while committing. You can review those changes. I changed int64 to integer for type check based on info here and here

@younik younik merged commit ff11da1 into Farama-Foundation:main Nov 17, 2023
6 checks passed
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.

3 participants