Source code for rl_mind.envs
"""Extra environments.
`CartPoleContinuous-v1` is the classic cart-pole environment with a
*continuous* action in :math:`[-1, 1]` (the force applied to the cart), used
by the DDPG/TD3/SAC notebooks. Adapted from ``bbrl_gymnasium`` (MIT license,
Sorbonne Université).
"""
import gymnasium as gym
import numpy as np
from gymnasium import spaces
from gymnasium.envs.classic_control.cartpole import CartPoleEnv
__all__ = ["ContinuousCartPoleEnv"]
[docs]
class ContinuousCartPoleEnv(CartPoleEnv):
"""Continuous-action version of the CartPole-v1 environment
Same observation space, reward (+1 per step) and termination condition as
`CartPole-v1`, but the action is the *signed fraction* of the maximum
force applied to the cart, a `Box(-1, 1, shape=(1,))`. Registered as
`CartPoleContinuous-v1` (500 steps at most) when `rl_mind.envs` is
imported.
"""
def __init__(self, **kwargs):
""":param kwargs: Passed to `gymnasium`'s `CartPoleEnv` (e.g.
`render_mode`)
"""
super().__init__(**kwargs)
self.action_space = spaces.Box(-1.0, 1.0, shape=(1,), dtype=np.float64)
[docs]
def step(self, action):
"""Apply a force to the cart and advance the simulation by one step
:param action: The force, as a fraction of `force_mag` in $[-1, 1]$
(clipped); a scalar or a one-element array
:returns: The gymnasium 5-tuple `(obs, reward, terminated, truncated,
info)`, with a reward of 1.0 at every step
"""
force_scale = float(np.clip(np.asarray(action), -1.0, 1.0).item())
assert self.state is not None, "Call reset before using step method."
x, x_dot, theta, theta_dot = self.state
force = self.force_mag * force_scale
costheta = np.cos(theta)
sintheta = np.sin(theta)
# Dynamics: https://coneural.org/florian/papers/05_cart_pole.pdf
temp = (
force + self.polemass_length * theta_dot**2 * sintheta
) / self.total_mass
thetaacc = (self.gravity * sintheta - costheta * temp) / (
self.length * (4.0 / 3.0 - self.masspole * costheta**2 / self.total_mass)
)
xacc = temp - self.polemass_length * thetaacc * costheta / self.total_mass
if self.kinematics_integrator == "euler":
x = x + self.tau * x_dot
x_dot = x_dot + self.tau * xacc
theta = theta + self.tau * theta_dot
theta_dot = theta_dot + self.tau * thetaacc
else: # semi-implicit euler
x_dot = x_dot + self.tau * xacc
x = x + self.tau * x_dot
theta_dot = theta_dot + self.tau * thetaacc
theta = theta + self.tau * theta_dot
self.state = (x, x_dot, theta, theta_dot)
terminated = bool(
x < -self.x_threshold
or x > self.x_threshold
or theta < -self.theta_threshold_radians
or theta > self.theta_threshold_radians
)
if self.render_mode == "human":
self.render()
return np.array(self.state, dtype=np.float32), 1.0, terminated, False, {}
# Importing this module registers the environments (an explicit
# `import rl_mind.envs` is the opt-in; the rl_mind package itself does not
# import it)
if "CartPoleContinuous-v1" not in gym.registry:
gym.register(
id="CartPoleContinuous-v1",
entry_point=ContinuousCartPoleEnv,
max_episode_steps=500,
)