From 3e517541b35e2be9fd6cf431aeb7d99983b1f46a Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Mon, 6 Nov 2023 08:35:54 +0100 Subject: [PATCH 01/26] initial change --- beginner_source/pendulum.py | 900 ++++++++++++++++++++++++++++++++++++ 1 file changed, 900 insertions(+) create mode 100644 beginner_source/pendulum.py diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py new file mode 100644 index 00000000000..f068c09f9b4 --- /dev/null +++ b/beginner_source/pendulum.py @@ -0,0 +1,900 @@ +# -*- coding: utf-8 -*- + +""" +Pendulum: Writing your environment and transforms with TorchRL +================= + +**Author**: `Vincent Moens `_ + +Creating an environment (a simulator or an interface to a physical control system) +is an integrative part of reinforcement learning and control engineering. + +TorchRL provides a set of tools to do this in multiple contexts. +This tutorial demonstrates how to use PyTorch and ``torchrl`` code a pendulum +simulator from the ground up. +It is freely inspired by the Pendulum-v1 implementation from `OpenAI-Gym/Farama-Gymnasium +control library `__. + +Key learnings: +- How to design an environment in TorchRL: + - Writing specs (input, observation and reward); + - Implementing behaviour: seeding, reset and step. +- Transforming your environment inputs and outputs, and writing your own + transforms; +- How to use :class:`tensordict.TensorDict` to carry arbitrary data structures + from step to step. + + In the process, we will touch three crucial components of TorchRL: + +* `environments `__ +* `transforms `__ +* `models (policy and value function) `__ + +""" + +###################################################################### +# To give a sense of what can be achieved with TorchRL's environments, we will +# be designing a *stateless* environment. While stateful environments keep track of +# the latest physical state encountered and rely on this to simulate the state-to-state +# transition, stateless environments expect the current state to be provided to +# them at each step, along with the action undertaken. TorchRL supports both +# types of environments, but stateless environments are more generic and hence +# cover a broader range of features of the environment API in torchrl. +# +# Modelling stateless environments gives users full control over the input and +# outputs of the simulator: one can reset an experiment at any stage. It also +# assumes that we have some control over a task, which may not always be the +# case: solving a problem where we cannot control the current state is more +# challenging but has a much wider set of applications. +# +# Another advantage of stateless environments is that they can enable +# batched execution of transition simulations. If the backend and the +# implementation allow it, an algebraic operation can be executed seamlessly on +# scalars, vectors or tensors. This tutorial gives such examples. +# +# This tutorial will be structured as follows: +# +# * We will first get acquainted with the environment properties: +# its shape (``batch_size``), its methods (mainly :meth:`EnvBase.step`, +# :meth:`EnvBase.reset` and :meth:`EnvBase.set_seed`) +# and finally its specs. +# * After having coded our simulator, we will demonstrate how it can be used +# during training with transforms. +# * We will explore new avenues that follow from the TorchRL's API, +# including: the possibility of transforming inputs, the vectorized execution +# of the simulation and the possibility of backpropagating through the +# simulation graph. +# * Finally, will train a simple policy to solve the system we implemented. +# +from collections import defaultdict +from typing import Optional + +import numpy as np +import torch +import tqdm +from tensordict.nn import TensorDictModule +from tensordict.tensordict import TensorDict, TensorDictBase +from torch import nn + +from torchrl.data import BoundedTensorSpec, CompositeSpec, UnboundedContinuousTensorSpec +from torchrl.envs import ( + CatTensors, + EnvBase, + Transform, + TransformedEnv, + UnsqueezeTransform, +) +from torchrl.envs.transforms.transforms import _apply_to_composite +from torchrl.envs.utils import check_env_specs, step_mdp + +DEFAULT_X = np.pi +DEFAULT_Y = 1.0 + +###################################################################### +# There are four things one must take care of when designing a new environment +# class: +# +# * :meth:`EnvBase._reset`, which codes for the resetting of the simulator +# at a (potentially random) initial state; +# * :meth:`EnvBase._step` which codes for the state transition dynamic; +# * :meth:`EnvBase._set_seed`` which implements the seeding mechanism; +# * the environment specs. +# +# Let us first describe the problem at hand: we would like to model a simple +# pendulum, over which we can control the torque applied on its fixed point. +# Our goal is to place the pendulum in upward position (angular position at 0 +# by convention) and having it standing still in that position. +# To design our dynamic system, we need to define two equations: the motion +# equation following an action (the torque applied) and the reward equation +# that will constitute our objective function. +# +# For the motion equation, we will update the angular velocity following: +# +# .. math:: +# +# \dot{\theta}_{t+1} = \dot{\theta}_t + (3 * g / (2 * L) * \sin(\theta_t) + 3 / (m * L^2) * u) * dt +# +# where :math:`\dot{\theta}` is the angular velocity in rad/sec, :math:`g` is the +# gravitational force, :math:`L` is the pendulum length, :math:`m` is its mass, +# :math:`\theta` is its angular position and :math:`u` is the torque. The +# angular position is then updated according to +# +# .. math:: +# +# \theta_{t+1} = \theta_{t} + \dot{\theta}_{t+1} dt +# +# We define our reward as +# +# .. math:: +# +# r = -(\theta^2 + 0.1 * \dot{\theta}^2 + 0.001 * u^2) +# +# which will be maximized when the angle is close to 0 (pendulum in upward +# position), the angular velocity is close to 0 (no motion) and the torque is +# 0 too. +# +# Coding the effect of an action: :func:`~torchrl.envs.EnvBase._step` +# ------------------------------------------------------------------- +# +# The step method is the first thing to consider, as it will encode +# the simulation that is of interest to us. In TorchRL, the +# :class:`~torchrl.envs.EnvBase` class has a :meth:`EnvBase.step` +# method that receives a :class:`tensordict.TensorDict` +# instance with an ``"action"`` entry indicating what action is to be taken. +# +# To facilitate the reading and writing from that tensordict and to make sure +# that the keys are consistent with what's expected from the library, the +# simulation part has been delegated to a private abstract method :meth:`_step` +# which reads input data from a tensordict, and writes a *new* tensordict +# with the output data. +# +# The :func:`_step` method should do the following: +# +# 1. read the input keys (such as ``"action"``) and execute the simulation +# based on these; +# 2. retrieve observations, done state and reward; +# 3. write the set of observation value along with the reward and done state +# at the corresponding entries in a new :class:`TensorDict`. +# +# Next, the :meth:`~torchrl.envs.EnvBase.step` method will merge the output +# of :meth:`~torchrl.envs.EnvBase.step` in the input tensordict to enforce +# input/output consistency. +# +# Typically, for stateful environments, this will look like +# +# .. code-block:: +# +# >>> policy(env.reset()) +# >>> print(tensordict) +# TensorDict( +# fields={ +# action: Tensor(shape=torch.Size([1]), device=cpu, dtype=torch.float32, is_shared=False), +# done: Tensor(shape=torch.Size([1]), device=cpu, dtype=torch.bool, is_shared=False), +# observation: Tensor(shape=torch.Size([]), device=cpu, dtype=torch.float32, is_shared=False)}, +# batch_size=torch.Size([]), +# device=cpu, +# is_shared=False) +# >>> env.step(tensordict) +# >>> print(tensordict) +# TensorDict( +# fields={ +# action: Tensor(shape=torch.Size([1]), device=cpu, dtype=torch.float32, is_shared=False), +# done: Tensor(shape=torch.Size([1]), device=cpu, dtype=torch.bool, is_shared=False), +# next: TensorDict( +# fields={ +# done: Tensor(shape=torch.Size([1]), device=cpu, dtype=torch.bool, is_shared=False), +# observation: Tensor(shape=torch.Size([]), device=cpu, dtype=torch.float32, is_shared=False), +# reward: Tensor(shape=torch.Size([1]), device=cpu, dtype=torch.float32, is_shared=False)}, +# batch_size=torch.Size([]), +# device=cpu, +# is_shared=False), +# observation: Tensor(shape=torch.Size([]), device=cpu, dtype=torch.float32, is_shared=False)}, +# batch_size=torch.Size([]), +# device=cpu, +# is_shared=False) +# +# Notice that the root tensordict has not changed, the only modification is the +# appearance of a new ``"next"`` entry that contains the new information. +# +# In the Pendulum example, our :meth:`_step` method will read the relevant +# entries from the input tensordict and compute the position and velocity of +# the pendulum after the force encoded by the ``"action"`` key has been applied +# onto it. We compute the new angular position of the pendulum +# ``"new_th"`` as the result of the previous position ``"th"`` plus the new +# velocity ``"new_thdot"`` over a time interval ``dt``. +# +# Since our goal is to turn the pendulum up and maintain it still in that +# position, our ``cost`` (negative reward) function is lower for positions +# close to the target and low speeds. +# Indeed, we want to discourage positions that are far from being "upward" +# and/or speeds that are far from 0. +# +# In our example, :meth:`EnvBase._step` is encoded as a static method since our +# environment is stateless. In stateful settings, the ``self`` argument is +# needed as the state needs to be read from the environment. +# + +def _step(tensordict): + th, thdot = tensordict["th"], tensordict["thdot"] # th := theta + + g_force = tensordict["params", "g"] + mass = tensordict["params", "m"] + length = tensordict["params", "l"] + dt = tensordict["params", "dt"] + u = tensordict["action"].squeeze(-1) + u = u.clamp(-tensordict["params", "max_torque"], tensordict["params", "max_torque"]) + costs = angle_normalize(th) ** 2 + 0.1 * thdot**2 + 0.001 * (u**2) + + new_thdot = ( + thdot + + (3 * g_force / (2 * length) * th.sin() + 3.0 / (mass * length**2) * u) * dt + ) + new_thdot = new_thdot.clamp( + -tensordict["params", "max_speed"], tensordict["params", "max_speed"] + ) + new_th = th + new_thdot * dt + reward = -costs.view(*tensordict.shape, 1) + done = torch.zeros_like(reward, dtype=torch.bool) + out = TensorDict( + { + "th": new_th, + "thdot": new_thdot, + "params": tensordict["params"], + "reward": reward, + "done": done, + }, + tensordict.shape, + ) + return out + + +def angle_normalize(x): + return ((x + torch.pi) % (2 * torch.pi)) - torch.pi + + +###################################################################### +# Resetting the simulator: :func:`~torchrl.envs.EnvBase._reset` +# ------------------------------------------------------------- +# +# The second method we need to care about is the +# :meth:`~torchrl.envs.EnvBase._reset` method. Like +# :meth:`~torchrl.envs.EnvBase._step`, it should write the observation entries +# and possibly a done state in the tensordict it outputs (if the done state is +# omitted, it will be filled as ``False`` by the parent method +# :meth:`~torchrl.envs.EnvBase.reset`). In some contexts, it is required that +# the ``_reset`` method receives a command from the function that called +# it (e.g. in multi-agent settings we may want to indicate which agents need +# to be reset). This is why the :meth:`~torchrl.envs.EnvBase._reset` method +# also expects a tensordict as input, albeit it may perfectly be empty or +# ``None``. +# +# The parent :meth:`EnvBase.reset` does some simple checks like the +# :meth:`EnvBase.step` does, such as making sure that a ``"done"`` state +# is returned in the output tensordict and that the shapes match what is +# expected from the specs. +# +# For us, the only important thing to consider is whether +# :meth:`EnvBase._reset` contains all the expected observations. Once more, +# since we are working with a stateless environment, we pass the configuration +# of the pendulum in a nested tensordict named ``"params"``. +# +# In this example, we do not pass a done state as this is not mandatory +# for :meth:`_reset` and our environment is non-terminating, so we always +# expect it to be ``False``. +# + + +def _reset(self, tensordict): + if tensordict is None or tensordict.is_empty(): + # if no tensordict is passed, we generate a single set of hyperparameters + # Otherwise, we assume that the input tensordict contains all the relevant + # parameters to get started. + tensordict = self.gen_params(batch_size=self.batch_size) + + high_th = torch.tensor(DEFAULT_X, device=self.device) + high_thdot = torch.tensor(DEFAULT_Y, device=self.device) + low_th = -high_th + low_thdot = -high_thdot + + # for non batch-locked envs, the input tensordict shape dictates the number + # of simulators run simultaneously. In other contexts, the initial + # random state's shape will depend upon the environment batch-size instead. + th = ( + torch.rand(tensordict.shape, generator=self.rng, device=self.device) + * (high_th - low_th) + + low_th + ) + thdot = ( + torch.rand(tensordict.shape, generator=self.rng, device=self.device) + * (high_thdot - low_thdot) + + low_thdot + ) + out = TensorDict( + { + "th": th, + "thdot": thdot, + "params": tensordict["params"], + }, + batch_size=tensordict.shape, + ) + return out + + +###################################################################### +# Environment metadata: ``env.*_spec`` +# ------------------------------------ +# +# The specs define the input and output domain of the environment. +# It is important that the specs accurately define the tensors that will be +# received at runtime, as they are often used to carry information about +# environments in multiprocessing and distributed settings. They can also be +# used to instantiate lazily defined neural networks and test scripts without +# actually querying the environment (which can be costly with real-world +# physical systems for instance). +# +# There are four specs that we must code in our environment: +# +# * :obj:`EnvBase.observation_spec`: This will be a :class:`~torchrl.data.CompositeSpec` +# instance where each key is an observation (a :class:`CompositeSpec` can be +# viewed as a dictionary of specs). +# * :obj:`EnvBase.action_spec`: It can be any type of spec, but it is required +# that it corresponds to the ``"action"`` entry in the input tensordict; +# * :obj:`EnvBase.reward_spec`: provides information about the reward space; +# * :obj:`EnvBase.done_spec`: provides information about the space of the done +# flag. +# +# TorchRL specs are organised in two general containers: ``input_spec`` which +# contains the specs of the information that the step function reads (divided +# between ``action_spec`` containing the action and ``state_spec`` containing +# all the rest), and ``output_spec`` which encodes the specs that the +# step outputs (``observation_spec``, ``reward_spec`` and ``done_spec``). +# In general, you should not interact directly with ``output_spec`` and +# ``input_spec`` but only with their content: ``observation_spec``, +# ``reward_spec``, ``done_spec``, ``action_spec`` and ``state_spec``. +# The reason if that the specs are organised in a non-trivial way +# within ``output_spec`` and +# ``input_spec`` and neither of these should be directly modified. +# +# In other words, the ``observation_spec`` and related properties are +# convenient shortcuts to the content of the output and input spec containers. +# +# TorchRL offers multiple :class:`~torchrl.data.TensorSpec` +# `subclasses `_ to +# encode the environment's input and output characteristics. +# +# Specs shape +# ^^^^^^^^^^^ +# +# The environment specs leading dimensions must match the +# environment batch-size. This is done to enforce that every component of an +# environment (including its transforms) have an accurate representation of +# the expected input and output shapes. This is something that should be +# accurately coded in stateful settings. +# +# For non batch-locked environments such as the one in our example (see below), +# this is irrelevant as the environment batch-size will most likely be empty. +# + + +def _make_spec(self, td_params): + # Under the hood, this will populate self.output_spec["observation"] + self.observation_spec = CompositeSpec( + th=BoundedTensorSpec( + low=-torch.pi, + high=torch.pi, + shape=(), + dtype=torch.float32, + ), + thdot=BoundedTensorSpec( + low=-td_params["params", "max_speed"], + high=td_params["params", "max_speed"], + shape=(), + dtype=torch.float32, + ), + # we need to add the "params" to the observation specs, as we want + # to pass it at each step during a rollout + params=make_composite_from_td(td_params["params"]), + shape=(), + ) + # since the environment is stateless, we expect the previous output as input. + # For this, EnvBase expects some state_spec to be available + self.state_spec = self.observation_spec.clone() + # action-spec will be automatically wrapped in input_spec when + # `self.action_spec = spec` will be called supported + self.action_spec = BoundedTensorSpec( + low=-td_params["params", "max_torque"], + high=td_params["params", "max_torque"], + shape=(1,), + dtype=torch.float32, + ) + self.reward_spec = UnboundedContinuousTensorSpec(shape=(*td_params.shape, 1)) + + +def make_composite_from_td(td): + # custom funtion to convert a tensordict in a similar spec structure + # of unbounded values. + composite = CompositeSpec( + { + key: make_composite_from_td(tensor) + if isinstance(tensor, TensorDictBase) + else UnboundedContinuousTensorSpec( + dtype=tensor.dtype, device=tensor.device, shape=tensor.shape + ) + for key, tensor in td.items() + }, + shape=td.shape, + ) + return composite + + +###################################################################### +# Reproducible experiments: seeding +# --------------------------------- +# +# Seeding an environment is a common operation when initializing an experiment. +# :func:`EnvBase._set_seed` only goal is to set the seed of the contained +# simulator. If possible, this operation should not call `reset()` or interact +# with the environment execution. The parent :func:`EnvBase.set_seed` method +# incorporates a mechanism that allows seeding multiple environments with a +# different pseudo-random and reproducible seed. +# + + +def _set_seed(self, seed: Optional[int]): + rng = torch.manual_seed(seed) + self.rng = rng + + +###################################################################### +# Wrapping things together: the :class:`~torchrl.envs.EnvBase` class +# ------------------------------------------------------------------ +# +# We can finally put together the pieces and design our environment class. +# The specs initialization needs to be performed during the environment +# construction, so we must take care of calling the :func:`_make_spec` method +# within :func:`PendulumEnv.__init__`. +# +# We add a static method :func:`PendulumEnv.gen_params` which deterministically +# generates a set of hyperparameters to be used during execution: +# + + +def gen_params(g=10.0, batch_size=None) -> TensorDictBase: + """Returns a tensordict containing the physical parameters such as gravitational force and torque or speed limits.""" + if batch_size is None: + batch_size = [] + td = TensorDict( + { + "params": TensorDict( + { + "max_speed": 8, + "max_torque": 2.0, + "dt": 0.05, + "g": g, + "m": 1.0, + "l": 1.0, + }, + [], + ) + }, + [], + ) + if batch_size: + td = td.expand(batch_size).contiguous() + return td + + +###################################################################### +# We define the environment as non-``batch_locked`` by turning the homonymous +# attribute to ``False``. This means that we will **not** enforce the input +# tensordict to have a batch-size that matches the one of the environment. +# +# The following code will just put together the pieces we have coded above. +# + + +class PendulumEnv(EnvBase): + metadata = { + "render_modes": ["human", "rgb_array"], + "render_fps": 30, + } + batch_locked = False + + def __init__(self, td_params=None, seed=None, device="cpu"): + if td_params is None: + td_params = self.gen_params() + + super().__init__(device=device, batch_size=[]) + self._make_spec(td_params) + if seed is None: + seed = torch.empty((), dtype=torch.int64).random_().item() + self.set_seed(seed) + + # Helpers: _make_step and gen_params + gen_params = staticmethod(gen_params) + _make_spec = _make_spec + + # Mandatory methods: _step, _reset and _set_seed + _reset = _reset + _step = staticmethod(_step) + _set_seed = _set_seed + + +###################################################################### +# Testing our environment +# ----------------------- +# +# TorchRL provides a simple function :func:`~torchrl.envs.utils.check_env_specs` +# to check that a (transformed) environment has an input/output structure that +# matches the one dictated by its specs. +# Let us try it out: +# + +env = PendulumEnv() +check_env_specs(env) + +###################################################################### +# We can have a look at our specs to have a visual representation of the environment +# signature: +# + +print("observation_spec:", env.observation_spec) +print("state_spec:", env.state_spec) +print("reward_spec:", env.reward_spec) + +###################################################################### +# We can execute a couple of commands too to check that the output structure +# matches what is expected. + +td = env.reset() +print("reset tensordict", td) + +###################################################################### +# We can run the :func:`env.rand_step` to generate +# an action randomly from the ``action_spec`` domain. A tensordict containing +# the hyperparams and the current state **must** be passed since our +# environment is stateless. In stateful contexts, ``env.rand_step()`` works +# perfectly too. +# +td = env.rand_step(td) +print("random step tensordict", td) + +###################################################################### +# Transforming an environment +# --------------------------- +# +# Writing environment transforms for stateless simulators is slightly more +# complicated than for stateful ones: transforming an output entry that needs +# to be read at the following iteration requires to apply the inverse transform +# before calling :func:`env.step` at the next step. +# This is an ideal scenario to showcase all the features of torchrl's +# transforms! +# +# For instance, in the following transformed environment we unsqueeze the entries +# ``["th", "thdot"]`` to be able to stack them along the last +# dimension. We also pass them as ``in_keys_inv`` to squeeze them back to their +# original shape once they are passed as input in the next iteration. +# +env = TransformedEnv( + env, + # Unsqueezes the observations that we will concatenate + UnsqueezeTransform( + unsqueeze_dim=-1, + in_keys=["th", "thdot"], + in_keys_inv=["th", "thdot"], + ), +) + +###################################################################### +# Writing custom transforms +# ^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# TorchRL's transforms may not cover all the operations one wants to execute +# after an environment has been executed. +# Writing a transform does not require much effort. As for the environment +# design, there are two steps in writing a transform: +# +# - Getting the dynamics right (forward and inverse); +# - Adapting the environment specs. +# +# A transform can be used in two settings: on its own, it can be used as a +# :class:`torch.nn.Module`. It can also be used appended to a +# :class:`~torchrl.envs.TransformedEnv`. The structure of the class allows to +# customize the behaviour in the different contexts. +# +# A :class:`~torchrl.envs.Transform` skeleton can be summarized as follows: +# +# .. code-block:: +# +# class Transform(nn.Module): +# def forward(self, tensordict): +# def _apply_transform(self, tensordict): +# def _step(self, tensordict): +# def _call(self, tensordict): +# def inv(self, tensordict): +# def _inv_apply_transform(self, tensordict): +# +# There are three entry points (:func:`forward`, :func:`_step` and :func:`inv`) +# which all receive :class:`tensordict.TensorDict` instances. The first two +# will eventually go through the keys indicated by :obj:`Transform.in_keys` +# and call :func:`Transform._apply_transform` to each of these. The results will +# be written in the entries pointed by :obj:`Transform.out_keys` if provided +# (if not the ``in_keys`` will be updated with the transformed values). +# If inverse transforms need to be executed, a similar data flow will be +# executed but with the :func:`Transform.inv` and +# :func:`Transform._inv_apply_transform` methods and across the ``in_keys_inv`` +# and ``out_keys_inv`` list of keys. +# The following figure summarized this flow for environments and replay +# buffers. +# +# .. figure:: /_static/img/transforms.png +# +# Transform API +# +# In some cases, a transform will not work on a subset of keys in a unitary +# manner, but will execute some operation on the parent environment or +# work with the entire input tensordict. +# In those cases, the :func:`_call` and :func:`forward` methods should be +# re-written, and the :func:`_apply_transform` method can be skipped. +# +# Let us code new transforms that will compute the ``sine`` and ``cosine`` +# values of the position angle, as these values are more useful to us to learn +# a policy than the raw angle value: + + +class SinTransform(Transform): + def _apply_transform(self, obs: torch.Tensor) -> None: + return obs.sin() + + # The transform must also modify the data at reset time + def _reset( + self, tensordict: TensorDictBase, tensordict_reset: TensorDictBase + ) -> TensorDictBase: + return self._call(tensordict_reset) + + # _apply_to_composite will execute the observation spec transform across all + # in_keys/out_keys pairs and write the result in the observation_spec which + # is of type ``Composite`` + @_apply_to_composite + def transform_observation_spec(self, observation_spec): + return BoundedTensorSpec( + low=-1, + high=1, + shape=observation_spec.shape, + dtype=observation_spec.dtype, + device=observation_spec.device, + ) + + +class CosTransform(Transform): + def _apply_transform(self, obs: torch.Tensor) -> None: + return obs.cos() + + # The transform must also modify the data at reset time + def _reset( + self, tensordict: TensorDictBase, tensordict_reset: TensorDictBase + ) -> TensorDictBase: + return self._call(tensordict_reset) + + # _apply_to_composite will execute the observation spec transform across all + # in_keys/out_keys pairs and write the result in the observation_spec which + # is of type ``Composite`` + @_apply_to_composite + def transform_observation_spec(self, observation_spec): + return BoundedTensorSpec( + low=-1, + high=1, + shape=observation_spec.shape, + dtype=observation_spec.dtype, + device=observation_spec.device, + ) + + +t_sin = SinTransform(in_keys=["th"], out_keys=["sin"]) +t_cos = CosTransform(in_keys=["th"], out_keys=["cos"]) +env.append_transform(t_sin) +env.append_transform(t_cos) + +###################################################################### +# Concatenates the observations onto an "observation" entry. +# del_keys=False ensures that we keep these values for the next +# iteration. +cat_transform = CatTensors( + in_keys=["sin", "cos", "thdot"], dim=-1, out_key="observation", del_keys=False +) +env.append_transform(cat_transform) + +###################################################################### +# Once more, let us check that our env specs match what is received: +check_env_specs(env) + +###################################################################### +# Executing a rollout +# ------------------- +# +# Executing a rollout is a succession of simple steps: +# +# * reset the environment +# * while some condition is not met: +# +# * compute an action given a policy +# * execute a step given this action +# * collect the data +# * make a MDP step +# +# * gather the data and return +# +# These operations have been convinently wrapped in the :func:`EnvBase.rollout` +# method, from which we provide a simplified version here below. + + +def simple_rollout(steps=100): + # preallocate: + data = TensorDict({}, [steps]) + # reset + _data = env.reset() + for i in range(steps): + _data["action"] = env.action_spec.rand() + _data = env.step(_data) + data[i] = _data + _data = step_mdp(_data, keep_other=True) + return data + + +print("data from rollout:", simple_rollout(100)) + +###################################################################### +# Batching computations +# --------------------- +# +# The last unexplored end of our tutorial is the ability that we have to +# batch computations in TorchRL. Because our environment does not +# make any assumptions regarding the input data shape, we can seamlessly +# execute it over batches of data. Even better: for non-batch-locked +# environments such as our Pendulum, we can change the batch size on the fly +# without recreating the env. +# To do this, we just generate parameters with the desired shape. +# + +batch_size = 10 # number of environments to be executed in batch +td = env.reset(env.gen_params(batch_size=[batch_size])) +print("reset (batch size of 10)", td) +td = env.rand_step(td) +print("rand step (batch size of 10)", td) + +###################################################################### +# executing a rollout with a batch of data requires us to reset the env +# out of the rollout function, since we need to define the batch_size +# dynamically and this is not supported by :func:`EnvBase.rollout`: +# + +rollout = env.rollout( + 3, + auto_reset=False, # we're executing the reset out of the ``rollout`` call + tensordict=env.reset(env.gen_params(batch_size=[batch_size])), +) +print("rollout of len 3 (batch size of 10):", rollout) + + +###################################################################### +# Training a simple policy +# ------------------------ +# +# In this example, we will train a simple policy using the reward as a +# differentiable objective (i.e. a negative loss). +# We will take advantage of the fact that our dynamic system is fully +# differentiable to backpropagate through the trajectory return and adjust the +# weights of our policy to maximise this value directly. Of course, in many +# settings many of the assumptions we make do not hold, such as +# differentiability of the system and full access to the underlying mechanics. +# +# Still, this is a very simple example that showcases how a training loop can +# be coded with a custom environment in TorchRL. +# +# Let us first write the policy network: +# +torch.manual_seed(0) +env.set_seed(0) + +net = nn.Sequential( + nn.LazyLinear(64), + nn.Tanh(), + nn.LazyLinear(64), + nn.Tanh(), + nn.LazyLinear(64), + nn.Tanh(), + nn.LazyLinear(1), +) +policy = TensorDictModule( + net, + in_keys=["observation"], + out_keys=["action"], +) + +###################################################################### +# and our optimizer: +# + +optim = torch.optim.Adam(policy.parameters(), lr=2e-3) + +###################################################################### +# Training loop +# ^^^^^^^^^^^^^ +# +# We will successively: +# +# * generate a trajectory +# * sum the rewards +# * backpropagate through the graph defined by these operations +# * clip the gradient norm and make an optimization step +# * repeat +# +# At the end of the training loop, we should have a final reward close to 0 +# which demonstrates that the pendulum is upward and still as desired. +# +batch_size = 32 +pbar = tqdm.tqdm(range(20_000 // batch_size)) +scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optim, 20_000) +logs = defaultdict(list) + +for _ in pbar: + init_td = env.reset(env.gen_params(batch_size=[batch_size])) + rollout = env.rollout(100, policy, tensordict=init_td, auto_reset=False) + traj_return = rollout["next", "reward"].mean() + (-traj_return).backward() + gn = torch.nn.utils.clip_grad_norm_(net.parameters(), 1.0) + optim.step() + optim.zero_grad() + pbar.set_description( + f"reward: {traj_return: 4.4f}, " + f"last reward: {rollout[..., -1]['next', 'reward'].mean(): 4.4f}, gradient norm: {gn: 4.4}" + ) + logs["return"].append(traj_return.item()) + logs["last_reward"].append(rollout[..., -1]["next", "reward"].mean().item()) + scheduler.step() + + +def plot(): + import matplotlib + from matplotlib import pyplot as plt + + is_ipython = "inline" in matplotlib.get_backend() + if is_ipython: + from IPython import display + + with plt.ion(): + plt.figure(figsize=(10, 5)) + plt.subplot(1, 2, 1) + plt.plot(logs["return"]) + plt.title("returns") + plt.xlabel("iteration") + plt.subplot(1, 2, 2) + plt.plot(logs["last_reward"]) + plt.title("last reward") + plt.xlabel("iteration") + if is_ipython: + display.display(plt.gcf()) + display.clear_output(wait=True) + plt.show() + + +plot() + + +###################################################################### +# Conclusion +# ---------- +# +# In this tutorial, we have learned how to code a stateless environment from +# scratch. We touched the subjects of: +# +# * the four essential components that need to be taken care of when coding +# an environment (:func:`step`, :func:`reset", seeding and building specs). +# We saw how these methods and classes interact with the +# :class:`tensordict.TensorDict` class; +# * how to test that an environment is properly coded using +# :func:`~torchrl.envs.utils.check_env_specs`; +# * How to append transforms in the context of stateless environments and how +# to write custom transformations; +# * How to train a policy on a fully differentiable simulator. +# From fc43523c670c6724ddac95112ce7756338e499c4 Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Mon, 6 Nov 2023 08:47:55 +0100 Subject: [PATCH 02/26] add link to RL tutorial port --- index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/index.rst b/index.rst index fb2ce3bd6f9..9a95743a6b2 100644 --- a/index.rst +++ b/index.rst @@ -948,6 +948,7 @@ Additional Resources :hidden: :caption: Reinforcement Learning + beginner/pendulum intermediate/reinforcement_q_learning intermediate/reinforcement_ppo intermediate/mario_rl_tutorial From ed84726b16b071618b923064e87daf8c64576e3d Mon Sep 17 00:00:00 2001 From: Krishna Kalyan Date: Mon, 6 Nov 2023 18:43:09 +0100 Subject: [PATCH 03/26] Update beginner_source/pendulum.py Co-authored-by: Svetlana Karslioglu --- beginner_source/pendulum.py | 1 + 1 file changed, 1 insertion(+) diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index f068c09f9b4..709932026fa 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -16,6 +16,7 @@ control library `__. Key learnings: + - How to design an environment in TorchRL: - Writing specs (input, observation and reward); - Implementing behaviour: seeding, reset and step. From 245512f43ef9c8958c7925a846da9e3b0820ff4e Mon Sep 17 00:00:00 2001 From: Krishna Kalyan Date: Mon, 6 Nov 2023 18:43:23 +0100 Subject: [PATCH 04/26] Update beginner_source/pendulum.py Co-authored-by: Svetlana Karslioglu --- beginner_source/pendulum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index 709932026fa..9e85351eedd 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -23,7 +23,7 @@ - Transforming your environment inputs and outputs, and writing your own transforms; - How to use :class:`tensordict.TensorDict` to carry arbitrary data structures - from step to step. + step-by-step. In the process, we will touch three crucial components of TorchRL: From a836e9b0ad853b0f2b1692504d12b15b5c77ede3 Mon Sep 17 00:00:00 2001 From: Krishna Kalyan Date: Mon, 6 Nov 2023 18:43:40 +0100 Subject: [PATCH 05/26] Update beginner_source/pendulum.py Co-authored-by: Svetlana Karslioglu --- beginner_source/pendulum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index 9e85351eedd..4fc527f3f5b 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -51,7 +51,7 @@ # Another advantage of stateless environments is that they can enable # batched execution of transition simulations. If the backend and the # implementation allow it, an algebraic operation can be executed seamlessly on -# scalars, vectors or tensors. This tutorial gives such examples. +# scalars, vectors, or tensors. This tutorial gives such examples. # # This tutorial will be structured as follows: # From 84612a962be6ed799c7a9bb6f46e6ef27217939d Mon Sep 17 00:00:00 2001 From: Krishna Kalyan Date: Mon, 6 Nov 2023 18:43:55 +0100 Subject: [PATCH 06/26] Update beginner_source/pendulum.py Co-authored-by: Svetlana Karslioglu --- beginner_source/pendulum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index 4fc527f3f5b..a6e69d27734 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -764,7 +764,7 @@ def simple_rollout(steps=100): print("rand step (batch size of 10)", td) ###################################################################### -# executing a rollout with a batch of data requires us to reset the env +# Executing a rollout with a batch of data requires us to reset the env # out of the rollout function, since we need to define the batch_size # dynamically and this is not supported by :func:`EnvBase.rollout`: # From c471ad1a4be6fc81dc3ef2b3a98a5c25419c0508 Mon Sep 17 00:00:00 2001 From: Krishna Kalyan Date: Mon, 6 Nov 2023 18:44:01 +0100 Subject: [PATCH 07/26] Update beginner_source/pendulum.py Co-authored-by: Svetlana Karslioglu --- beginner_source/pendulum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index a6e69d27734..b3e777de48f 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -889,7 +889,7 @@ def plot(): # In this tutorial, we have learned how to code a stateless environment from # scratch. We touched the subjects of: # -# * the four essential components that need to be taken care of when coding +# * The four essential components that need to be taken care of when coding # an environment (:func:`step`, :func:`reset", seeding and building specs). # We saw how these methods and classes interact with the # :class:`tensordict.TensorDict` class; From 456ac8f244bc6f157f74d302d159ee8584b11cb8 Mon Sep 17 00:00:00 2001 From: Krishna Kalyan Date: Mon, 6 Nov 2023 18:44:11 +0100 Subject: [PATCH 08/26] Update beginner_source/pendulum.py Co-authored-by: Svetlana Karslioglu --- beginner_source/pendulum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index b3e777de48f..2bf1bc46a83 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -893,7 +893,7 @@ def plot(): # an environment (:func:`step`, :func:`reset", seeding and building specs). # We saw how these methods and classes interact with the # :class:`tensordict.TensorDict` class; -# * how to test that an environment is properly coded using +# * How to test that an environment is properly coded using # :func:`~torchrl.envs.utils.check_env_specs`; # * How to append transforms in the context of stateless environments and how # to write custom transformations; From 8abdf16c59efe691b45022a4c839e1343baae647 Mon Sep 17 00:00:00 2001 From: Krishna Kalyan Date: Mon, 6 Nov 2023 18:44:20 +0100 Subject: [PATCH 09/26] Update beginner_source/pendulum.py Co-authored-by: Svetlana Karslioglu --- beginner_source/pendulum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index 2bf1bc46a83..36ac7caa9a1 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -782,7 +782,7 @@ def simple_rollout(steps=100): # ------------------------ # # In this example, we will train a simple policy using the reward as a -# differentiable objective (i.e. a negative loss). +# differentiable objective, such as a negative loss. # We will take advantage of the fact that our dynamic system is fully # differentiable to backpropagate through the trajectory return and adjust the # weights of our policy to maximise this value directly. Of course, in many From 399ba37fdefd0ba66112616ca9b08347aa447056 Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Mon, 6 Nov 2023 18:49:57 +0100 Subject: [PATCH 10/26] suggestions fix --- beginner_source/pendulum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index 36ac7caa9a1..01cd49107bd 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -2,7 +2,7 @@ """ Pendulum: Writing your environment and transforms with TorchRL -================= +============================================================== **Author**: `Vincent Moens `_ @@ -11,7 +11,7 @@ TorchRL provides a set of tools to do this in multiple contexts. This tutorial demonstrates how to use PyTorch and ``torchrl`` code a pendulum -simulator from the ground up. +in a simulator environment from the ground up. It is freely inspired by the Pendulum-v1 implementation from `OpenAI-Gym/Farama-Gymnasium control library `__. From d8af37a9e67847df68c7f6e2850c1bacf9ba4cdf Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Mon, 6 Nov 2023 18:57:41 +0100 Subject: [PATCH 11/26] add image --- _static/img/pendulum.gif | Bin 0 -> 125364 bytes beginner_source/pendulum.py | 5 +++++ 2 files changed, 5 insertions(+) create mode 100644 _static/img/pendulum.gif diff --git a/_static/img/pendulum.gif b/_static/img/pendulum.gif new file mode 100644 index 0000000000000000000000000000000000000000..a7adf181fc8a4037591849b1c9d17bec88ed7ad2 GIT binary patch literal 125364 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
    o!Zu!)3TBSmTN6j#; zNRDI(I6%L-xXpsoDMCSH?Q$a-mn*AF!)6eZ8Q4Of=Qh_Qv+JnEYnB5W!*i{sAr_bH z5r2J=-U2=VYC!T2lQ5Ai_J`K#cRSbLs_#GZ@PD(!{<;i_wI)Z&O>F$4i6Tsh-M>Bh zL>FMHPlzVlnq9E{sAqS1uNbz9LLQS+iW-~rL7JU3Q#f=h)BzCaWiG_3b9OMb^f?_f zbfHW~Mkt0#Ii0#rbkFfk<<&$JMCVu$j9RTlRUiwWbmV&9FYoFinOy2G@I68%0Hmr0`eA;c*tL$588+z40x<)x30j_@VicvYaz*(P^^t7;p7Z`2&$tj04w6{)e52%pOfYo@}~C8X(2-tQK+ z=4~pbR@%6SSGnMv&XXGsZO2N}e5mT+e`s40wYlzfKR+c4PSP>=#HGcH)e1y#u_C2h1>syyAjasQDLTgyrc6X=p=jmG{*c^lJJu&_@M=#3W;X4{DCZO1 zmPZOV%^h3$B)0>KuSL^l=SgV2yx2$$E+`lwyEDmB`m&;<{+R-1(0ghhZ!eO!7B05q zs+3M!)Y>vlw>D1Z9MvgpAIf%gWa?#P+xVV6D(9e^`f)zt$qkoE5W(k)O5m8;%NdDb zvNS!5am%Qg+6mh+YGfwnyZ+L0_~b@tFd|@Q6>L{(T@HiCDH3wd<|Ir@vR%k!(;u>B zFAUh7%#G~b65B;Myk%wi+9JZymiFnq)1KntY{>4nqlI|M@36o5b$>+yBoOxl68u2~ z@EZ*LXvpw868ugU{qqp@XPW5$wG*S10)xQx{0O3^R)B(*kvD<)WB~-iLQQLw6MhyX z+QK-b1i}^`4dq=BQ4Zy3seF+QabMzAloCV~YHKmRgSEOnruwN=7<ZJ%93Y#!UXl-mZ4gCQLlq@ykK8KDj;K*xgfQn3U`WFjD?4d7@gjI;wTCXG&i^)Z$onA_b#U4hho7to*=;-DJaUa%u7OA# z1cdtMDch{w7lR1u6$}2+%RHn^h*!s}`5`+eYen%Ar|V@n@6(N{%7)X;I>Pthzy8p- zfo&j&n9C6j{J@?hE$5*MdS#xWkY&lNsj=I3MU9y6ZQI_SOW+x7OK~pqT0k9yV6aJT$mC`rZTa;XMIW zh+o#T+L~_;bD0p-pWOMm1rx7`?3&oU0ffbi6@6{nUVLJ}7GSZ-aoM$rPvov9=hUV- zsPYQKyDm;wt9llJ-M4g;Fs~vBl27!5wtEAuy~KzstfIfen?Xx{(N|sT`XpMVwOa<= zH%-9$s<5*CB9-63GIbO``cW#B+ecH#_N7D^z6`~EX%q+3=D+I2%%Hrogt5QSyuE%uH$u40;!`T)L59_N& zqD6$|(aAK{38HobQm+7iHYo{p{v!dsWu#9rG=9h#7vW&Wnt(bM`~A-GBBvx%^^8zW4#(hwfReF z#?Zd{CW)2`=Q|vhy6OYAEcbM+T5QRB$+ynmy`nw`t1DJPnM*MSWHmf+_heHXMBXnl z))UuwlS0W`YtXMTE@b;)5*dH#@SP*h>u}1ovi@+|ldyd_LqSFgDE$w?W318u literal 0 HcmV?d00001 diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index 01cd49107bd..e324447ecac 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -15,6 +15,11 @@ It is freely inspired by the Pendulum-v1 implementation from `OpenAI-Gym/Farama-Gymnasium control library `__. +.. figure:: /_static/img/pendulum.gif + :alt: Pendulum + + Simple Pendulum + Key learnings: - How to design an environment in TorchRL: From 4dd4110f39fe90b7f60477d7ef4ded628e045cf6 Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Tue, 7 Nov 2023 07:44:17 +0100 Subject: [PATCH 12/26] spell check --- beginner_source/pendulum.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index e324447ecac..c4272c5750c 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -24,7 +24,7 @@ - How to design an environment in TorchRL: - Writing specs (input, observation and reward); - - Implementing behaviour: seeding, reset and step. + - Implementing behavior: seeding, reset and step. - Transforming your environment inputs and outputs, and writing your own transforms; - How to use :class:`tensordict.TensorDict` to carry arbitrary data structures @@ -47,7 +47,7 @@ # types of environments, but stateless environments are more generic and hence # cover a broader range of features of the environment API in torchrl. # -# Modelling stateless environments gives users full control over the input and +# modelling stateless environments gives users full control over the input and # outputs of the simulator: one can reset an experiment at any stage. It also # assumes that we have some control over a task, which may not always be the # case: solving a problem where we cannot control the current state is more @@ -68,7 +68,7 @@ # during training with transforms. # * We will explore new avenues that follow from the TorchRL's API, # including: the possibility of transforming inputs, the vectorized execution -# of the simulation and the possibility of backpropagating through the +# of the simulation and the possibility of backpropagation through the # simulation graph. # * Finally, will train a simple policy to solve the system we implemented. # @@ -606,7 +606,7 @@ def __init__(self, td_params=None, seed=None, device="cpu"): # A transform can be used in two settings: on its own, it can be used as a # :class:`torch.nn.Module`. It can also be used appended to a # :class:`~torchrl.envs.TransformedEnv`. The structure of the class allows to -# customize the behaviour in the different contexts. +# customize the behavior in the different contexts. # # A :class:`~torchrl.envs.Transform` skeleton can be summarized as follows: # @@ -730,7 +730,7 @@ def transform_observation_spec(self, observation_spec): # # * gather the data and return # -# These operations have been convinently wrapped in the :func:`EnvBase.rollout` +# These operations have been conveniently wrapped in the :func:`EnvBase.rollout` # method, from which we provide a simplified version here below. @@ -790,7 +790,7 @@ def simple_rollout(steps=100): # differentiable objective, such as a negative loss. # We will take advantage of the fact that our dynamic system is fully # differentiable to backpropagate through the trajectory return and adjust the -# weights of our policy to maximise this value directly. Of course, in many +# weights of our policy to maximize this value directly. Of course, in many # settings many of the assumptions we make do not hold, such as # differentiability of the system and full access to the underlying mechanics. # From 940af6c6171cf535bba45adb2332be6e97375f22 Mon Sep 17 00:00:00 2001 From: Krishna Kalyan Date: Tue, 7 Nov 2023 07:48:47 +0100 Subject: [PATCH 13/26] Apply suggestions from code review Changes from @svekars Co-authored-by: Svetlana Karslioglu --- beginner_source/pendulum.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index c4272c5750c..fbc68448607 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -70,7 +70,7 @@ # including: the possibility of transforming inputs, the vectorized execution # of the simulation and the possibility of backpropagation through the # simulation graph. -# * Finally, will train a simple policy to solve the system we implemented. +# * Finally, we will train a simple policy to solve the system we implemented. # from collections import defaultdict from typing import Optional @@ -97,7 +97,7 @@ DEFAULT_Y = 1.0 ###################################################################### -# There are four things one must take care of when designing a new environment +# There are four things you must take care of when designing a new environment # class: # # * :meth:`EnvBase._reset`, which codes for the resetting of the simulator @@ -107,7 +107,7 @@ # * the environment specs. # # Let us first describe the problem at hand: we would like to model a simple -# pendulum, over which we can control the torque applied on its fixed point. +# pendulum over which we can control the torque applied on its fixed point. # Our goal is to place the pendulum in upward position (angular position at 0 # by convention) and having it standing still in that position. # To design our dynamic system, we need to define two equations: the motion @@ -156,17 +156,17 @@ # # The :func:`_step` method should do the following: # -# 1. read the input keys (such as ``"action"``) and execute the simulation +# 1. Read the input keys (such as ``"action"``) and execute the simulation # based on these; -# 2. retrieve observations, done state and reward; -# 3. write the set of observation value along with the reward and done state +# 2. Retrieve observations, done state and reward; +# 3. Write the set of observation values along with the reward and done state # at the corresponding entries in a new :class:`TensorDict`. # # Next, the :meth:`~torchrl.envs.EnvBase.step` method will merge the output -# of :meth:`~torchrl.envs.EnvBase.step` in the input tensordict to enforce +# of :meth:`~torchrl.envs.EnvBase.step` in the input ``tensordict`` to enforce # input/output consistency. # -# Typically, for stateful environments, this will look like +# Typically, for stateful environments, this will look like this: # # .. code-block:: # @@ -199,7 +199,7 @@ # device=cpu, # is_shared=False) # -# Notice that the root tensordict has not changed, the only modification is the +# Notice that the root ``tensordict`` has not changed, the only modification is the # appearance of a new ``"next"`` entry that contains the new information. # # In the Pendulum example, our :meth:`_step` method will read the relevant @@ -269,7 +269,7 @@ def angle_normalize(x): # omitted, it will be filled as ``False`` by the parent method # :meth:`~torchrl.envs.EnvBase.reset`). In some contexts, it is required that # the ``_reset`` method receives a command from the function that called -# it (e.g. in multi-agent settings we may want to indicate which agents need +# it (for example, in multi-agent settings we may want to indicate which agents need # to be reset). This is why the :meth:`~torchrl.envs.EnvBase._reset` method # also expects a tensordict as input, albeit it may perfectly be empty or # ``None``. @@ -377,8 +377,8 @@ def _reset(self, tensordict): # the expected input and output shapes. This is something that should be # accurately coded in stateful settings. # -# For non batch-locked environments such as the one in our example (see below), -# this is irrelevant as the environment batch-size will most likely be empty. +# For non batch-locked environments, such as the one in our example (see below), +# this is irrelevant as the environment batch size will most likely be empty. # @@ -438,8 +438,8 @@ def make_composite_from_td(td): # --------------------------------- # # Seeding an environment is a common operation when initializing an experiment. -# :func:`EnvBase._set_seed` only goal is to set the seed of the contained -# simulator. If possible, this operation should not call `reset()` or interact +# The only goal of :func:`EnvBase._set_seed` is to set the seed of the contained +# simulator. If possible, this operation should not call ``reset()`` or interact # with the environment execution. The parent :func:`EnvBase.set_seed` method # incorporates a mechanism that allows seeding multiple environments with a # different pseudo-random and reproducible seed. @@ -493,7 +493,7 @@ def gen_params(g=10.0, batch_size=None) -> TensorDictBase: ###################################################################### # We define the environment as non-``batch_locked`` by turning the homonymous # attribute to ``False``. This means that we will **not** enforce the input -# tensordict to have a batch-size that matches the one of the environment. +# ``tensordict`` to have a ``batch-size`` that matches the one of the environment. # # The following code will just put together the pieces we have coded above. # @@ -703,7 +703,7 @@ def transform_observation_spec(self, observation_spec): ###################################################################### # Concatenates the observations onto an "observation" entry. -# del_keys=False ensures that we keep these values for the next +# ``del_keys=False`` ensures that we keep these values for the next # iteration. cat_transform = CatTensors( in_keys=["sin", "cos", "thdot"], dim=-1, out_key="observation", del_keys=False From b7392f5dac4db7aa64aa18b2cf5a3ed9643cb330 Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Tue, 7 Nov 2023 17:54:59 +0100 Subject: [PATCH 14/26] update spell check --- beginner_source/pendulum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/pendulum.py b/beginner_source/pendulum.py index fbc68448607..616d0680734 100644 --- a/beginner_source/pendulum.py +++ b/beginner_source/pendulum.py @@ -792,7 +792,7 @@ def simple_rollout(steps=100): # differentiable to backpropagate through the trajectory return and adjust the # weights of our policy to maximize this value directly. Of course, in many # settings many of the assumptions we make do not hold, such as -# differentiability of the system and full access to the underlying mechanics. +# differentiable system and full access to the underlying mechanics. # # Still, this is a very simple example that showcases how a training loop can # be coded with a custom environment in TorchRL. From 443aeea889a20ec68d94027c8d6ad3da6cf54d37 Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Tue, 7 Nov 2023 18:03:15 +0100 Subject: [PATCH 15/26] move files --- {beginner_source => intermediate_source}/pendulum.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {beginner_source => intermediate_source}/pendulum.py (100%) diff --git a/beginner_source/pendulum.py b/intermediate_source/pendulum.py similarity index 100% rename from beginner_source/pendulum.py rename to intermediate_source/pendulum.py From b0c3c63bf3e203221bca21eee0ced0d4280333e4 Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Tue, 7 Nov 2023 20:00:19 +0100 Subject: [PATCH 16/26] update refs --- index.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/index.rst b/index.rst index 9a95743a6b2..66ee3c1bcef 100644 --- a/index.rst +++ b/index.rst @@ -319,7 +319,12 @@ What's new in PyTorch tutorials? :link: advanced/coding_ddpg.html :tags: Reinforcement-Learning - +.. customcarditem:: + :header: Writing your environment and transforms + :card_description: Use TorchRL to code a Pendulum + :image: _static/img/pendulum.gif + :link: intermediate/pendulum.html + :tags: Reinforcement-Learning .. Deploying PyTorch Models in Production @@ -948,7 +953,7 @@ Additional Resources :hidden: :caption: Reinforcement Learning - beginner/pendulum + intermediate/pendulum intermediate/reinforcement_q_learning intermediate/reinforcement_ppo intermediate/mario_rl_tutorial From d89bc0de84017bf2779453c5096d395e80110594 Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Tue, 7 Nov 2023 20:06:03 +0100 Subject: [PATCH 17/26] update refs --- intermediate_source/pendulum.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/intermediate_source/pendulum.py b/intermediate_source/pendulum.py index 616d0680734..cd0c2158b0a 100644 --- a/intermediate_source/pendulum.py +++ b/intermediate_source/pendulum.py @@ -10,8 +10,8 @@ is an integrative part of reinforcement learning and control engineering. TorchRL provides a set of tools to do this in multiple contexts. -This tutorial demonstrates how to use PyTorch and ``torchrl`` code a pendulum -in a simulator environment from the ground up. +This tutorial demonstrates how to use PyTorch and TorchRL code a pendulum +simulator from the ground up. It is freely inspired by the Pendulum-v1 implementation from `OpenAI-Gym/Farama-Gymnasium control library `__. @@ -27,8 +27,8 @@ - Implementing behavior: seeding, reset and step. - Transforming your environment inputs and outputs, and writing your own transforms; -- How to use :class:`tensordict.TensorDict` to carry arbitrary data structures - step-by-step. +- How to use :class:`~tensordict.TensorDict` to carry arbitrary data structures + through the codebase. In the process, we will touch three crucial components of TorchRL: @@ -45,7 +45,7 @@ # transition, stateless environments expect the current state to be provided to # them at each step, along with the action undertaken. TorchRL supports both # types of environments, but stateless environments are more generic and hence -# cover a broader range of features of the environment API in torchrl. +# cover a broader range of features of the environment API in TorchRL. # # modelling stateless environments gives users full control over the input and # outputs of the simulator: one can reset an experiment at any stage. It also @@ -61,8 +61,8 @@ # This tutorial will be structured as follows: # # * We will first get acquainted with the environment properties: -# its shape (``batch_size``), its methods (mainly :meth:`EnvBase.step`, -# :meth:`EnvBase.reset` and :meth:`EnvBase.set_seed`) +# its shape (``batch_size``), its methods (mainly :meth:`~torchrl.envs.EnvBase.step`, +# :meth:`~torchrl.envs.EnvBase.reset` and :meth:`~torchrl.envs.EnvBase.set_seed`) # and finally its specs. # * After having coded our simulator, we will demonstrate how it can be used # during training with transforms. @@ -633,7 +633,6 @@ def __init__(self, td_params=None, seed=None, device="cpu"): # The following figure summarized this flow for environments and replay # buffers. # -# .. figure:: /_static/img/transforms.png # # Transform API # From 00790b1e371272e7ffd604386901fdc0defcd650 Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Tue, 7 Nov 2023 20:07:45 +0100 Subject: [PATCH 18/26] update to advanced refs --- {intermediate_source => advanced_source}/pendulum.py | 0 index.rst | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename {intermediate_source => advanced_source}/pendulum.py (100%) diff --git a/intermediate_source/pendulum.py b/advanced_source/pendulum.py similarity index 100% rename from intermediate_source/pendulum.py rename to advanced_source/pendulum.py diff --git a/index.rst b/index.rst index 66ee3c1bcef..363b33c03b6 100644 --- a/index.rst +++ b/index.rst @@ -323,7 +323,7 @@ What's new in PyTorch tutorials? :header: Writing your environment and transforms :card_description: Use TorchRL to code a Pendulum :image: _static/img/pendulum.gif - :link: intermediate/pendulum.html + :link: advanced/pendulum.html :tags: Reinforcement-Learning .. Deploying PyTorch Models in Production @@ -953,7 +953,7 @@ Additional Resources :hidden: :caption: Reinforcement Learning - intermediate/pendulum + advanced/pendulum intermediate/reinforcement_q_learning intermediate/reinforcement_ppo intermediate/mario_rl_tutorial From 8f1d6b4f066150a0f44989ae7d310c7b67d0988d Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Tue, 7 Nov 2023 20:20:14 +0100 Subject: [PATCH 19/26] suggestions --- advanced_source/pendulum.py | 40 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/advanced_source/pendulum.py b/advanced_source/pendulum.py index cd0c2158b0a..f3b9a3dcb28 100644 --- a/advanced_source/pendulum.py +++ b/advanced_source/pendulum.py @@ -47,11 +47,11 @@ # types of environments, but stateless environments are more generic and hence # cover a broader range of features of the environment API in TorchRL. # -# modelling stateless environments gives users full control over the input and -# outputs of the simulator: one can reset an experiment at any stage. It also -# assumes that we have some control over a task, which may not always be the -# case: solving a problem where we cannot control the current state is more -# challenging but has a much wider set of applications. +# Modelling stateless environments gives users full control over the input and +# outputs of the simulator: one can reset an experiment at any stage or actively +# modify the dynamics from the outside. However, it assumes that we have some control +# over a task, which may not always be the case: solving a problem where we cannot +# control the current state is more challenging but has a much wider set of applications. # # Another advantage of stateless environments is that they can enable # batched execution of transition simulations. If the backend and the @@ -460,7 +460,7 @@ def _set_seed(self, seed: Optional[int]): # construction, so we must take care of calling the :func:`_make_spec` method # within :func:`PendulumEnv.__init__`. # -# We add a static method :func:`PendulumEnv.gen_params` which deterministically +# We add a static method :meth:`PendulumEnv.gen_params` which deterministically # generates a set of hyperparameters to be used during execution: # @@ -572,8 +572,8 @@ def __init__(self, td_params=None, seed=None, device="cpu"): # Writing environment transforms for stateless simulators is slightly more # complicated than for stateful ones: transforming an output entry that needs # to be read at the following iteration requires to apply the inverse transform -# before calling :func:`env.step` at the next step. -# This is an ideal scenario to showcase all the features of torchrl's +# before calling :func:`meth.step` at the next step. +# This is an ideal scenario to showcase all the features of TorchRL's # transforms! # # For instance, in the following transformed environment we unsqueeze the entries @@ -604,26 +604,32 @@ def __init__(self, td_params=None, seed=None, device="cpu"): # - Adapting the environment specs. # # A transform can be used in two settings: on its own, it can be used as a -# :class:`torch.nn.Module`. It can also be used appended to a -# :class:`~torchrl.envs.TransformedEnv`. The structure of the class allows to +# :class:`~torch.nn.Module`. It can also be used appended to a +# :class:`~torchrl.envs.transforms.TransformedEnv`. The structure of the class allows to # customize the behavior in the different contexts. # -# A :class:`~torchrl.envs.Transform` skeleton can be summarized as follows: +# A :class:`~torchrl.envs.transforms.Transform` skeleton can be summarized as follows: # # .. code-block:: # # class Transform(nn.Module): # def forward(self, tensordict): +# ... # def _apply_transform(self, tensordict): +# ... # def _step(self, tensordict): +# ... # def _call(self, tensordict): +# ... # def inv(self, tensordict): +# ... # def _inv_apply_transform(self, tensordict): +# ... # # There are three entry points (:func:`forward`, :func:`_step` and :func:`inv`) # which all receive :class:`tensordict.TensorDict` instances. The first two -# will eventually go through the keys indicated by :obj:`Transform.in_keys` -# and call :func:`Transform._apply_transform` to each of these. The results will +# will eventually go through the keys indicated by :obj:`~tochrl.envs.transforms.Transform.in_keys` +# and call :meth:`~torchrl.envs.transforms.Transform._apply_transform` to each of these. The results will # be written in the entries pointed by :obj:`Transform.out_keys` if provided # (if not the ``in_keys`` will be updated with the transformed values). # If inverse transforms need to be executed, a similar data flow will be @@ -729,7 +735,7 @@ def transform_observation_spec(self, observation_spec): # # * gather the data and return # -# These operations have been conveniently wrapped in the :func:`EnvBase.rollout` +# These operations have been conveniently wrapped in the :meth:`~torchrl.envs.EnvBase.rollout` # method, from which we provide a simplified version here below. @@ -770,7 +776,7 @@ def simple_rollout(steps=100): ###################################################################### # Executing a rollout with a batch of data requires us to reset the env # out of the rollout function, since we need to define the batch_size -# dynamically and this is not supported by :func:`EnvBase.rollout`: +# dynamically and this is not supported by :meth:`~torchrl.envs.EnvBase.rollout`: # rollout = env.rollout( @@ -894,9 +900,9 @@ def plot(): # scratch. We touched the subjects of: # # * The four essential components that need to be taken care of when coding -# an environment (:func:`step`, :func:`reset", seeding and building specs). +# an environment (``step``, ``reset``, seeding and building specs). # We saw how these methods and classes interact with the -# :class:`tensordict.TensorDict` class; +# :class:`~tensordict.TensorDict` class; # * How to test that an environment is properly coded using # :func:`~torchrl.envs.utils.check_env_specs`; # * How to append transforms in the context of stateless environments and how From bef8a75c3b1e67077b683cf97aea276afb9e1aeb Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Tue, 7 Nov 2023 22:24:10 +0100 Subject: [PATCH 20/26] move to end --- index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.rst b/index.rst index 363b33c03b6..c6211e7eaaa 100644 --- a/index.rst +++ b/index.rst @@ -953,10 +953,10 @@ Additional Resources :hidden: :caption: Reinforcement Learning - advanced/pendulum intermediate/reinforcement_q_learning intermediate/reinforcement_ppo intermediate/mario_rl_tutorial + advanced/pendulum .. toctree:: :maxdepth: 2 From 970b8cf28ebf20fb1ac816758d55bc785dcc02f3 Mon Sep 17 00:00:00 2001 From: Svetlana Karslioglu Date: Tue, 7 Nov 2023 14:47:03 -0800 Subject: [PATCH 21/26] Update advanced_source/pendulum.py --- advanced_source/pendulum.py | 1 + 1 file changed, 1 insertion(+) diff --git a/advanced_source/pendulum.py b/advanced_source/pendulum.py index f3b9a3dcb28..23f683ca11c 100644 --- a/advanced_source/pendulum.py +++ b/advanced_source/pendulum.py @@ -17,6 +17,7 @@ .. figure:: /_static/img/pendulum.gif :alt: Pendulum + :align: center Simple Pendulum From d2566dad3dd012390dbf499b72f420d929759fd0 Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Wed, 8 Nov 2023 10:04:51 +0100 Subject: [PATCH 22/26] spell check --- advanced_source/pendulum.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/advanced_source/pendulum.py b/advanced_source/pendulum.py index 23f683ca11c..4732a828c8e 100644 --- a/advanced_source/pendulum.py +++ b/advanced_source/pendulum.py @@ -398,7 +398,7 @@ def _make_spec(self, td_params): shape=(), dtype=torch.float32, ), - # we need to add the "params" to the observation specs, as we want + # we need to add the ``params`` to the observation specs, as we want # to pass it at each step during a rollout params=make_composite_from_td(td_params["params"]), shape=(), @@ -418,7 +418,7 @@ def _make_spec(self, td_params): def make_composite_from_td(td): - # custom funtion to convert a tensordict in a similar spec structure + # custom function to convert a ``tensordict`` in a similar spec structure # of unbounded values. composite = CompositeSpec( { @@ -558,8 +558,8 @@ def __init__(self, td_params=None, seed=None, device="cpu"): ###################################################################### # We can run the :func:`env.rand_step` to generate -# an action randomly from the ``action_spec`` domain. A tensordict containing -# the hyperparams and the current state **must** be passed since our +# an action randomly from the ``action_spec`` domain. A ``tensordict`` containing +# the hyperparameters and the current state **must** be passed since our # environment is stateless. In stateful contexts, ``env.rand_step()`` works # perfectly too. # @@ -577,14 +577,14 @@ def __init__(self, td_params=None, seed=None, device="cpu"): # This is an ideal scenario to showcase all the features of TorchRL's # transforms! # -# For instance, in the following transformed environment we unsqueeze the entries +# For instance, in the following transformed environment we ``unsqueeze`` the entries # ``["th", "thdot"]`` to be able to stack them along the last # dimension. We also pass them as ``in_keys_inv`` to squeeze them back to their # original shape once they are passed as input in the next iteration. # env = TransformedEnv( env, - # Unsqueezes the observations that we will concatenate + # ``Unsqueeze`` the observations that we will concatenate UnsqueezeTransform( unsqueeze_dim=-1, in_keys=["th", "thdot"], @@ -717,7 +717,7 @@ def transform_observation_spec(self, observation_spec): env.append_transform(cat_transform) ###################################################################### -# Once more, let us check that our env specs match what is received: +# Once more, let us check that our environment specs match what is received: check_env_specs(env) ###################################################################### @@ -764,7 +764,7 @@ def simple_rollout(steps=100): # make any assumptions regarding the input data shape, we can seamlessly # execute it over batches of data. Even better: for non-batch-locked # environments such as our Pendulum, we can change the batch size on the fly -# without recreating the env. +# without recreating the environment. # To do this, we just generate parameters with the desired shape. # @@ -775,7 +775,7 @@ def simple_rollout(steps=100): print("rand step (batch size of 10)", td) ###################################################################### -# Executing a rollout with a batch of data requires us to reset the env +# Executing a rollout with a batch of data requires us to reset the environment # out of the rollout function, since we need to define the batch_size # dynamically and this is not supported by :meth:`~torchrl.envs.EnvBase.rollout`: # From 7c1596ba70a7bf89592a3aae9873b22b573dd049 Mon Sep 17 00:00:00 2001 From: krishnakalyan3 Date: Wed, 8 Nov 2023 18:54:13 +0100 Subject: [PATCH 23/26] fix spell --- advanced_source/pendulum.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/advanced_source/pendulum.py b/advanced_source/pendulum.py index 4732a828c8e..a991a6f6ae2 100644 --- a/advanced_source/pendulum.py +++ b/advanced_source/pendulum.py @@ -29,7 +29,7 @@ - Transforming your environment inputs and outputs, and writing your own transforms; - How to use :class:`~tensordict.TensorDict` to carry arbitrary data structures - through the codebase. + through the ``codebase``. In the process, we will touch three crucial components of TorchRL: @@ -149,10 +149,10 @@ # method that receives a :class:`tensordict.TensorDict` # instance with an ``"action"`` entry indicating what action is to be taken. # -# To facilitate the reading and writing from that tensordict and to make sure +# To facilitate the reading and writing from that ``tensordict`` and to make sure # that the keys are consistent with what's expected from the library, the # simulation part has been delegated to a private abstract method :meth:`_step` -# which reads input data from a tensordict, and writes a *new* tensordict +# which reads input data from a ``tensordict``, and writes a *new* ``tensordict`` # with the output data. # # The :func:`_step` method should do the following: @@ -204,7 +204,7 @@ # appearance of a new ``"next"`` entry that contains the new information. # # In the Pendulum example, our :meth:`_step` method will read the relevant -# entries from the input tensordict and compute the position and velocity of +# entries from the input ``tensordict`` and compute the position and velocity of # the pendulum after the force encoded by the ``"action"`` key has been applied # onto it. We compute the new angular position of the pendulum # ``"new_th"`` as the result of the previous position ``"th"`` plus the new @@ -266,24 +266,24 @@ def angle_normalize(x): # The second method we need to care about is the # :meth:`~torchrl.envs.EnvBase._reset` method. Like # :meth:`~torchrl.envs.EnvBase._step`, it should write the observation entries -# and possibly a done state in the tensordict it outputs (if the done state is +# and possibly a done state in the ``tensordict`` it outputs (if the done state is # omitted, it will be filled as ``False`` by the parent method # :meth:`~torchrl.envs.EnvBase.reset`). In some contexts, it is required that # the ``_reset`` method receives a command from the function that called # it (for example, in multi-agent settings we may want to indicate which agents need # to be reset). This is why the :meth:`~torchrl.envs.EnvBase._reset` method -# also expects a tensordict as input, albeit it may perfectly be empty or +# also expects a ``tensordict`` as input, albeit it may perfectly be empty or # ``None``. # # The parent :meth:`EnvBase.reset` does some simple checks like the # :meth:`EnvBase.step` does, such as making sure that a ``"done"`` state -# is returned in the output tensordict and that the shapes match what is +# is returned in the output ``tensordict`` and that the shapes match what is # expected from the specs. # # For us, the only important thing to consider is whether # :meth:`EnvBase._reset` contains all the expected observations. Once more, # since we are working with a stateless environment, we pass the configuration -# of the pendulum in a nested tensordict named ``"params"``. +# of the pendulum in a nested ``tensordict`` named ``"params"``. # # In this example, we do not pass a done state as this is not mandatory # for :meth:`_reset` and our environment is non-terminating, so we always @@ -345,12 +345,12 @@ def _reset(self, tensordict): # instance where each key is an observation (a :class:`CompositeSpec` can be # viewed as a dictionary of specs). # * :obj:`EnvBase.action_spec`: It can be any type of spec, but it is required -# that it corresponds to the ``"action"`` entry in the input tensordict; +# that it corresponds to the ``"action"`` entry in the input ``tensordict``; # * :obj:`EnvBase.reward_spec`: provides information about the reward space; # * :obj:`EnvBase.done_spec`: provides information about the space of the done # flag. # -# TorchRL specs are organised in two general containers: ``input_spec`` which +# TorchRL specs are organized in two general containers: ``input_spec`` which # contains the specs of the information that the step function reads (divided # between ``action_spec`` containing the action and ``state_spec`` containing # all the rest), and ``output_spec`` which encodes the specs that the @@ -358,7 +358,7 @@ def _reset(self, tensordict): # In general, you should not interact directly with ``output_spec`` and # ``input_spec`` but only with their content: ``observation_spec``, # ``reward_spec``, ``done_spec``, ``action_spec`` and ``state_spec``. -# The reason if that the specs are organised in a non-trivial way +# The reason if that the specs are organized in a non-trivial way # within ``output_spec`` and # ``input_spec`` and neither of these should be directly modified. # @@ -404,7 +404,7 @@ def _make_spec(self, td_params): shape=(), ) # since the environment is stateless, we expect the previous output as input. - # For this, EnvBase expects some state_spec to be available + # For this, ``EnvBase`` expects some state_spec to be available self.state_spec = self.observation_spec.clone() # action-spec will be automatically wrapped in input_spec when # `self.action_spec = spec` will be called supported @@ -467,7 +467,7 @@ def _set_seed(self, seed: Optional[int]): def gen_params(g=10.0, batch_size=None) -> TensorDictBase: - """Returns a tensordict containing the physical parameters such as gravitational force and torque or speed limits.""" + """Returns a ``tensordict`` containing the physical parameters such as gravitational force and torque or speed limits.""" if batch_size is None: batch_size = [] td = TensorDict( @@ -492,7 +492,7 @@ def gen_params(g=10.0, batch_size=None) -> TensorDictBase: ###################################################################### -# We define the environment as non-``batch_locked`` by turning the homonymous +# We define the environment as non-``batch_locked`` by turning the ``homonymous`` # attribute to ``False``. This means that we will **not** enforce the input # ``tensordict`` to have a ``batch-size`` that matches the one of the environment. # @@ -645,7 +645,7 @@ def __init__(self, td_params=None, seed=None, device="cpu"): # # In some cases, a transform will not work on a subset of keys in a unitary # manner, but will execute some operation on the parent environment or -# work with the entire input tensordict. +# work with the entire input ``tensordict``. # In those cases, the :func:`_call` and :func:`forward` methods should be # re-written, and the :func:`_apply_transform` method can be skipped. # @@ -732,7 +732,7 @@ def transform_observation_spec(self, observation_spec): # * compute an action given a policy # * execute a step given this action # * collect the data -# * make a MDP step +# * make a ``MDP`` step # # * gather the data and return # From a003084ee3417ddf29ef2241a692c543a9a34fd9 Mon Sep 17 00:00:00 2001 From: Svetlana Karslioglu Date: Wed, 8 Nov 2023 15:26:52 -0800 Subject: [PATCH 24/26] Update advanced_source/pendulum.py --- advanced_source/pendulum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/advanced_source/pendulum.py b/advanced_source/pendulum.py index a991a6f6ae2..2eeb5be0982 100644 --- a/advanced_source/pendulum.py +++ b/advanced_source/pendulum.py @@ -48,7 +48,7 @@ # types of environments, but stateless environments are more generic and hence # cover a broader range of features of the environment API in TorchRL. # -# Modelling stateless environments gives users full control over the input and +# Modeling stateless environments gives users full control over the input and # outputs of the simulator: one can reset an experiment at any stage or actively # modify the dynamics from the outside. However, it assumes that we have some control # over a task, which may not always be the case: solving a problem where we cannot From b1748117fa01fd93489c0298f3ccf4110d497dea Mon Sep 17 00:00:00 2001 From: Svetlana Karslioglu Date: Wed, 8 Nov 2023 15:28:35 -0800 Subject: [PATCH 25/26] Update advanced_source/pendulum.py --- advanced_source/pendulum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/advanced_source/pendulum.py b/advanced_source/pendulum.py index 2eeb5be0982..9be6eee3137 100644 --- a/advanced_source/pendulum.py +++ b/advanced_source/pendulum.py @@ -303,7 +303,7 @@ def _reset(self, tensordict): low_th = -high_th low_thdot = -high_thdot - # for non batch-locked envs, the input tensordict shape dictates the number + # for non batch-locked envs, the input ``tensordict`` shape dictates the number # of simulators run simultaneously. In other contexts, the initial # random state's shape will depend upon the environment batch-size instead. th = ( From fc6afea8553e63a0b9cf2ef77e0e3fc2f086570b Mon Sep 17 00:00:00 2001 From: Krishna Kalyan Date: Thu, 9 Nov 2023 10:20:58 +0100 Subject: [PATCH 26/26] spell check --- advanced_source/pendulum.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/advanced_source/pendulum.py b/advanced_source/pendulum.py index 9be6eee3137..9f7a7f75626 100644 --- a/advanced_source/pendulum.py +++ b/advanced_source/pendulum.py @@ -293,8 +293,8 @@ def angle_normalize(x): def _reset(self, tensordict): if tensordict is None or tensordict.is_empty(): - # if no tensordict is passed, we generate a single set of hyperparameters - # Otherwise, we assume that the input tensordict contains all the relevant + # if no ``tensordict`` is passed, we generate a single set of hyperparameters + # Otherwise, we assume that the input ``tensordict`` contains all the relevant # parameters to get started. tensordict = self.gen_params(batch_size=self.batch_size) @@ -303,7 +303,7 @@ def _reset(self, tensordict): low_th = -high_th low_thdot = -high_thdot - # for non batch-locked envs, the input ``tensordict`` shape dictates the number + # for non batch-locked environments, the input ``tensordict`` shape dictates the number # of simulators run simultaneously. In other contexts, the initial # random state's shape will depend upon the environment batch-size instead. th = (