restructuring
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Union, Tuple
|
||||
from abc import ABC
|
||||
from typing import Tuple
|
||||
|
||||
import gym
|
||||
import numpy as np
|
||||
@@ -7,77 +7,77 @@ from gym import spaces
|
||||
from mp_pytorch.mp.mp_interfaces import MPInterface
|
||||
|
||||
from alr_envs.mp.controllers.base_controller import BaseController
|
||||
from alr_envs.mp.raw_interface_wrapper import RawInterfaceWrapper
|
||||
|
||||
|
||||
class EpisodicWrapper(gym.Env, gym.wrappers.TransformReward, ABC):
|
||||
"""
|
||||
Base class for movement primitive based gym.Wrapper implementations.
|
||||
class BlackBoxWrapper(gym.ObservationWrapper, ABC):
|
||||
|
||||
Args:
|
||||
env: The (wrapped) environment this wrapper is applied on
|
||||
num_dof: Dimension of the action space of the wrapped env
|
||||
num_basis: Number of basis functions per dof
|
||||
duration: Length of the trajectory of the movement primitive in seconds
|
||||
controller: Type or object defining the policy that is used to generate action based on the trajectory
|
||||
weight_scale: Scaling parameter for the actions given to this wrapper
|
||||
render_mode: Equivalent to gym render mode
|
||||
"""
|
||||
def __init__(self,
|
||||
env: RawInterfaceWrapper,
|
||||
trajectory_generator: MPInterface, tracking_controller: BaseController,
|
||||
duration: float, verbose: int = 1, sequencing=True, reward_aggregation: callable = np.sum):
|
||||
"""
|
||||
gym.Wrapper for leveraging a black box approach with a trajectory generator.
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
env: gym.Env,
|
||||
mp: MPInterface,
|
||||
controller: BaseController,
|
||||
duration: float,
|
||||
render_mode: str = None,
|
||||
verbose: int = 1,
|
||||
weight_scale: float = 1,
|
||||
sequencing=True,
|
||||
reward_aggregation=np.mean,
|
||||
):
|
||||
Args:
|
||||
env: The (wrapped) environment this wrapper is applied on
|
||||
trajectory_generator: Generates the full or partial trajectory
|
||||
tracking_controller: Translates the desired trajectory to raw action sequences
|
||||
duration: Length of the trajectory of the movement primitive in seconds
|
||||
verbose: level of detail for returned values in info dict.
|
||||
reward_aggregation: function that takes the np.ndarray of step rewards as input and returns the trajectory
|
||||
reward, default summation over all values.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.env = env
|
||||
try:
|
||||
self.dt = env.dt
|
||||
except AttributeError:
|
||||
raise AttributeError("step based environment needs to have a function 'dt' ")
|
||||
self.duration = duration
|
||||
self.traj_steps = int(duration / self.dt)
|
||||
self.post_traj_steps = self.env.spec.max_episode_steps - self.traj_steps
|
||||
# duration = self.env.max_episode_steps * self.dt
|
||||
|
||||
self.mp = mp
|
||||
self.env = env
|
||||
self.controller = controller
|
||||
self.weight_scale = weight_scale
|
||||
|
||||
# rendering
|
||||
self.render_mode = render_mode
|
||||
self.render_kwargs = {}
|
||||
# trajectory generation
|
||||
self.trajectory_generator = trajectory_generator
|
||||
self.tracking_controller = tracking_controller
|
||||
# self.weight_scale = weight_scale
|
||||
self.time_steps = np.linspace(0, self.duration, self.traj_steps)
|
||||
self.mp.set_mp_times(self.time_steps)
|
||||
# self.mp.set_mp_duration(self.time_steps, dt)
|
||||
# action_bounds = np.inf * np.ones((np.prod(self.mp.num_params)))
|
||||
self.mp_action_space = self.get_mp_action_space()
|
||||
self.trajectory_generator.set_mp_times(self.time_steps)
|
||||
# self.trajectory_generator.set_mp_duration(self.time_steps, dt)
|
||||
# action_bounds = np.inf * np.ones((np.prod(self.trajectory_generator.num_params)))
|
||||
self.reward_aggregation = reward_aggregation
|
||||
|
||||
# spaces
|
||||
self.mp_action_space = self.get_mp_action_space()
|
||||
self.action_space = self.get_action_space()
|
||||
self.active_obs = self.set_active_obs()
|
||||
self.observation_space = spaces.Box(low=self.env.observation_space.low[self.active_obs],
|
||||
high=self.env.observation_space.high[self.active_obs],
|
||||
self.observation_space = spaces.Box(low=self.env.observation_space.low[self.env.context_mask],
|
||||
high=self.env.observation_space.high[self.env.context_mask],
|
||||
dtype=self.env.observation_space.dtype)
|
||||
|
||||
# rendering
|
||||
self.render_mode = None
|
||||
self.render_kwargs = {}
|
||||
|
||||
self.verbose = verbose
|
||||
|
||||
@property
|
||||
def dt(self):
|
||||
return self.env.dt
|
||||
|
||||
def observation(self, observation):
|
||||
return observation[self.env.context_mask]
|
||||
|
||||
def get_trajectory(self, action: np.ndarray) -> Tuple:
|
||||
# TODO: this follows the implementation of the mp_pytorch library which includes the parameters tau and delay at
|
||||
# the beginning of the array.
|
||||
ignore_indices = int(self.mp.learn_tau) + int(self.mp.learn_delay)
|
||||
scaled_mp_params = action.copy()
|
||||
scaled_mp_params[ignore_indices:] *= self.weight_scale
|
||||
self.mp.set_params(np.clip(scaled_mp_params, self.mp_action_space.low, self.mp_action_space.high))
|
||||
self.mp.set_boundary_conditions(bc_time=self.time_steps[:1], bc_pos=self.current_pos, bc_vel=self.current_vel)
|
||||
traj_dict = self.mp.get_mp_trajs(get_pos=True, get_vel=True)
|
||||
# ignore_indices = int(self.trajectory_generator.learn_tau) + int(self.trajectory_generator.learn_delay)
|
||||
# scaled_mp_params = action.copy()
|
||||
# scaled_mp_params[ignore_indices:] *= self.weight_scale
|
||||
|
||||
clipped_params = np.clip(action, self.mp_action_space.low, self.mp_action_space.high)
|
||||
self.trajectory_generator.set_params(clipped_params)
|
||||
self.trajectory_generator.set_boundary_conditions(bc_time=self.time_steps[:1], bc_pos=self.current_pos,
|
||||
bc_vel=self.current_vel)
|
||||
traj_dict = self.trajectory_generator.get_mp_trajs(get_pos=True, get_vel=True)
|
||||
trajectory_tensor, velocity_tensor = traj_dict['pos'], traj_dict['vel']
|
||||
|
||||
trajectory = trajectory_tensor.numpy()
|
||||
@@ -86,13 +86,13 @@ class EpisodicWrapper(gym.Env, gym.wrappers.TransformReward, ABC):
|
||||
# TODO: Do we need this or does mp_pytorch have this?
|
||||
if self.post_traj_steps > 0:
|
||||
trajectory = np.vstack([trajectory, np.tile(trajectory[-1, :], [self.post_traj_steps, 1])])
|
||||
velocity = np.vstack([velocity, np.zeros(shape=(self.post_traj_steps, self.mp.num_dof))])
|
||||
velocity = np.vstack([velocity, np.zeros(shape=(self.post_traj_steps, self.trajectory_generator.num_dof))])
|
||||
|
||||
return trajectory, velocity
|
||||
|
||||
def get_mp_action_space(self):
|
||||
"""This function can be used to set up an individual space for the parameters of the mp."""
|
||||
min_action_bounds, max_action_bounds = self.mp.get_param_bounds()
|
||||
"""This function can be used to set up an individual space for the parameters of the trajectory_generator."""
|
||||
min_action_bounds, max_action_bounds = self.trajectory_generator.get_param_bounds()
|
||||
mp_action_space = gym.spaces.Box(low=min_action_bounds.numpy(), high=max_action_bounds.numpy(),
|
||||
dtype=np.float32)
|
||||
return mp_action_space
|
||||
@@ -109,71 +109,6 @@ class EpisodicWrapper(gym.Env, gym.wrappers.TransformReward, ABC):
|
||||
except AttributeError:
|
||||
return self.get_mp_action_space()
|
||||
|
||||
def _episode_callback(self, action: np.ndarray) -> Tuple[np.ndarray, Union[np.ndarray, None]]:
|
||||
"""
|
||||
Used to extract the parameters for the motion primitive and other parameters from an action array which might
|
||||
include other actions like ball releasing time for the beer pong environment.
|
||||
This only needs to be overwritten if the action space is modified.
|
||||
Args:
|
||||
action: a vector instance of the whole action space, includes mp parameters and additional parameters if
|
||||
specified, else only mp parameters
|
||||
|
||||
Returns:
|
||||
Tuple: mp_arguments and other arguments
|
||||
"""
|
||||
return action, None
|
||||
|
||||
def _step_callback(self, t: int, env_spec_params: Union[np.ndarray, None], step_action: np.ndarray) -> Union[
|
||||
np.ndarray]:
|
||||
"""
|
||||
This function can be used to modify the step_action with additional parameters e.g. releasing the ball in the
|
||||
Beerpong env. The parameters used should not be part of the motion primitive parameters.
|
||||
Returns step_action by default, can be overwritten in individual mp_wrappers.
|
||||
Args:
|
||||
t: the current time step of the episode
|
||||
env_spec_params: the environment specific parameter, as defined in fucntion _episode_callback
|
||||
(e.g. ball release time in Beer Pong)
|
||||
step_action: the current step-based action
|
||||
|
||||
Returns:
|
||||
modified step action
|
||||
"""
|
||||
return step_action
|
||||
|
||||
@abstractmethod
|
||||
def set_active_obs(self) -> np.ndarray:
|
||||
"""
|
||||
This function defines the contexts. The contexts are defined as specific observations.
|
||||
Returns:
|
||||
boolearn array representing the indices of the observations
|
||||
|
||||
"""
|
||||
return np.ones(self.env.observation_space.shape[0], dtype=bool)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def current_pos(self) -> Union[float, int, np.ndarray, Tuple]:
|
||||
"""
|
||||
Returns the current position of the action/control dimension.
|
||||
The dimensionality has to match the action/control dimension.
|
||||
This is not required when exclusively using velocity control,
|
||||
it should, however, be implemented regardless.
|
||||
E.g. The joint positions that are directly or indirectly controlled by the action.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def current_vel(self) -> Union[float, int, np.ndarray, Tuple]:
|
||||
"""
|
||||
Returns the current velocity of the action/control dimension.
|
||||
The dimensionality has to match the action/control dimension.
|
||||
This is not required when exclusively using position control,
|
||||
it should, however, be implemented regardless.
|
||||
E.g. The joint velocities that are directly or indirectly controlled by the action.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def step(self, action: np.ndarray):
|
||||
""" This function generates a trajectory based on a MP and then does the usual loop over reset and step"""
|
||||
# TODO: Think about sequencing
|
||||
@@ -184,46 +119,52 @@ class EpisodicWrapper(gym.Env, gym.wrappers.TransformReward, ABC):
|
||||
|
||||
# TODO
|
||||
# self.time_steps = np.linspace(0, learned_duration, self.traj_steps)
|
||||
# self.mp.set_mp_times(self.time_steps)
|
||||
# self.trajectory_generator.set_mp_times(self.time_steps)
|
||||
|
||||
trajectory_length = len(trajectory)
|
||||
rewards = np.zeros(shape=(trajectory_length,))
|
||||
if self.verbose >= 2:
|
||||
actions = np.zeros(shape=(trajectory_length,) + self.env.action_space.shape)
|
||||
observations = np.zeros(shape=(trajectory_length,) + self.env.observation_space.shape,
|
||||
dtype=self.env.observation_space.dtype)
|
||||
rewards = np.zeros(shape=(trajectory_length,))
|
||||
trajectory_return = 0
|
||||
|
||||
infos = dict()
|
||||
done = False
|
||||
|
||||
for t, pos_vel in enumerate(zip(trajectory, velocity)):
|
||||
step_action = self.controller.get_action(pos_vel[0], pos_vel[1], self.current_pos, self.current_vel)
|
||||
step_action = self.tracking_controller.get_action(pos_vel[0], pos_vel[1], self.current_pos,
|
||||
self.current_vel)
|
||||
step_action = self._step_callback(t, env_spec_params, step_action) # include possible callback info
|
||||
c_action = np.clip(step_action, self.env.action_space.low, self.env.action_space.high)
|
||||
# print('step/clipped action ratio: ', step_action/c_action)
|
||||
obs, c_reward, done, info = self.env.step(c_action)
|
||||
rewards[t] = c_reward
|
||||
|
||||
if self.verbose >= 2:
|
||||
actions[t, :] = c_action
|
||||
rewards[t] = c_reward
|
||||
observations[t, :] = obs
|
||||
trajectory_return += c_reward
|
||||
|
||||
for k, v in info.items():
|
||||
elems = infos.get(k, [None] * trajectory_length)
|
||||
elems[t] = v
|
||||
infos[k] = elems
|
||||
# infos['step_infos'].append(info)
|
||||
if self.render_mode:
|
||||
|
||||
if self.render_mode is not None:
|
||||
self.render(mode=self.render_mode, **self.render_kwargs)
|
||||
if done or do_replanning(kwargs):
|
||||
|
||||
if done or self.env.do_replanning(self.env.current_pos, self.env.current_vel, obs, c_action, t):
|
||||
break
|
||||
|
||||
infos.update({k: v[:t + 1] for k, v in infos.items()})
|
||||
|
||||
if self.verbose >= 2:
|
||||
infos['trajectory'] = trajectory
|
||||
infos['step_actions'] = actions[:t + 1]
|
||||
infos['step_observations'] = observations[:t + 1]
|
||||
infos['step_rewards'] = rewards[:t + 1]
|
||||
|
||||
infos['trajectory_length'] = t + 1
|
||||
done = True
|
||||
trajectory_return = self.reward_aggregation(rewards[:t + 1])
|
||||
return self.get_observation_from_step(obs), trajectory_return, done, infos
|
||||
|
||||
def reset(self):
|
||||
@@ -6,8 +6,8 @@ from alr_envs.mp.controllers.base_controller import BaseController
|
||||
class MetaWorldController(BaseController):
|
||||
"""
|
||||
A Metaworld Controller. Using position and velocity information from a provided environment,
|
||||
the controller calculates a response based on the desired position and velocity.
|
||||
Unlike the other Controllers, this is a special controller for MetaWorld environments.
|
||||
the tracking_controller calculates a response based on the desired position and velocity.
|
||||
Unlike the other Controllers, this is a special tracking_controller for MetaWorld environments.
|
||||
They use a position delta for the xyz coordinates and a raw position for the gripper opening.
|
||||
|
||||
:param env: A position environment
|
||||
|
||||
@@ -6,7 +6,7 @@ from alr_envs.mp.controllers.base_controller import BaseController
|
||||
class PDController(BaseController):
|
||||
"""
|
||||
A PD-Controller. Using position and velocity information from a provided environment,
|
||||
the controller calculates a response based on the desired position and velocity
|
||||
the tracking_controller calculates a response based on the desired position and velocity
|
||||
|
||||
:param env: A position environment
|
||||
:param p_gains: Factors for the proportional gains
|
||||
|
||||
@@ -3,7 +3,7 @@ from alr_envs.mp.controllers.base_controller import BaseController
|
||||
|
||||
class PosController(BaseController):
|
||||
"""
|
||||
A Position Controller. The controller calculates a response only based on the desired position.
|
||||
A Position Controller. The tracking_controller calculates a response only based on the desired position.
|
||||
"""
|
||||
def get_action(self, des_pos, des_vel, c_pos, c_vel):
|
||||
return des_pos
|
||||
|
||||
@@ -3,7 +3,7 @@ from alr_envs.mp.controllers.base_controller import BaseController
|
||||
|
||||
class VelController(BaseController):
|
||||
"""
|
||||
A Velocity Controller. The controller calculates a response only based on the desired velocity.
|
||||
A Velocity Controller. The tracking_controller calculates a response only based on the desired velocity.
|
||||
"""
|
||||
def get_action(self, des_pos, des_vel, c_pos, c_vel):
|
||||
return des_vel
|
||||
|
||||
@@ -7,16 +7,16 @@ from mp_pytorch.basis_gn.basis_generator import BasisGenerator
|
||||
ALL_TYPES = ["promp", "dmp", "idmp"]
|
||||
|
||||
|
||||
def get_movement_primitive(
|
||||
movement_primitives_type: str, action_dim: int, basis_generator: BasisGenerator, **kwargs
|
||||
def get_trajectory_generator(
|
||||
trajectory_generator_type: str, action_dim: int, basis_generator: BasisGenerator, **kwargs
|
||||
):
|
||||
movement_primitives_type = movement_primitives_type.lower()
|
||||
if movement_primitives_type == "promp":
|
||||
trajectory_generator_type = trajectory_generator_type.lower()
|
||||
if trajectory_generator_type == "promp":
|
||||
return ProMP(basis_generator, action_dim, **kwargs)
|
||||
elif movement_primitives_type == "dmp":
|
||||
elif trajectory_generator_type == "dmp":
|
||||
return DMP(basis_generator, action_dim, **kwargs)
|
||||
elif movement_primitives_type == 'idmp':
|
||||
elif trajectory_generator_type == 'idmp':
|
||||
return IDMP(basis_generator, action_dim, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Specified movement primitive type {movement_primitives_type} not supported, "
|
||||
raise ValueError(f"Specified movement primitive type {trajectory_generator_type} not supported, "
|
||||
f"please choose one of {ALL_TYPES}.")
|
||||
@@ -0,0 +1,88 @@
|
||||
from typing import Union, Tuple
|
||||
|
||||
import gym
|
||||
import numpy as np
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
class RawInterfaceWrapper(gym.Wrapper):
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def context_mask(self) -> np.ndarray:
|
||||
"""
|
||||
This function defines the contexts. The contexts are defined as specific observations.
|
||||
Returns:
|
||||
bool array representing the indices of the observations
|
||||
|
||||
"""
|
||||
return np.ones(self.env.observation_space.shape[0], dtype=bool)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def current_pos(self) -> Union[float, int, np.ndarray, Tuple]:
|
||||
"""
|
||||
Returns the current position of the action/control dimension.
|
||||
The dimensionality has to match the action/control dimension.
|
||||
This is not required when exclusively using velocity control,
|
||||
it should, however, be implemented regardless.
|
||||
E.g. The joint positions that are directly or indirectly controlled by the action.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def current_vel(self) -> Union[float, int, np.ndarray, Tuple]:
|
||||
"""
|
||||
Returns the current velocity of the action/control dimension.
|
||||
The dimensionality has to match the action/control dimension.
|
||||
This is not required when exclusively using position control,
|
||||
it should, however, be implemented regardless.
|
||||
E.g. The joint velocities that are directly or indirectly controlled by the action.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def dt(self) -> float:
|
||||
"""
|
||||
Control frequency of the environment
|
||||
Returns: float
|
||||
|
||||
"""
|
||||
|
||||
def do_replanning(self, pos, vel, s, a, t):
|
||||
# return t % 100 == 0
|
||||
# return bool(self.replanning_model(s))
|
||||
return False
|
||||
|
||||
def _episode_callback(self, action: np.ndarray) -> Tuple[np.ndarray, Union[np.ndarray, None]]:
|
||||
"""
|
||||
Used to extract the parameters for the motion primitive and other parameters from an action array which might
|
||||
include other actions like ball releasing time for the beer pong environment.
|
||||
This only needs to be overwritten if the action space is modified.
|
||||
Args:
|
||||
action: a vector instance of the whole action space, includes trajectory_generator parameters and additional parameters if
|
||||
specified, else only trajectory_generator parameters
|
||||
|
||||
Returns:
|
||||
Tuple: mp_arguments and other arguments
|
||||
"""
|
||||
return action, None
|
||||
|
||||
def _step_callback(self, t: int, env_spec_params: Union[np.ndarray, None], step_action: np.ndarray) -> Union[
|
||||
np.ndarray]:
|
||||
"""
|
||||
This function can be used to modify the step_action with additional parameters e.g. releasing the ball in the
|
||||
Beerpong env. The parameters used should not be part of the motion primitive parameters.
|
||||
Returns step_action by default, can be overwritten in individual mp_wrappers.
|
||||
Args:
|
||||
t: the current time step of the episode
|
||||
env_spec_params: the environment specific parameter, as defined in function _episode_callback
|
||||
(e.g. ball release time in Beer Pong)
|
||||
step_action: the current step-based action
|
||||
|
||||
Returns:
|
||||
modified step action
|
||||
"""
|
||||
return step_action
|
||||
Reference in New Issue
Block a user