fancy_gym/alr_envs/mp/black_box_wrapper.py

203 lines
9.0 KiB
Python
Raw Normal View History

2022-06-29 09:37:18 +02:00
from abc import ABC
from typing import Tuple
2022-04-28 09:05:28 +02:00
import gym
import numpy as np
from gym import spaces
from mp_pytorch.mp.mp_interfaces import MPInterface
2022-05-03 19:51:54 +02:00
from alr_envs.mp.controllers.base_controller import BaseController
2022-06-29 09:37:18 +02:00
from alr_envs.mp.raw_interface_wrapper import RawInterfaceWrapper
2022-05-03 19:51:54 +02:00
2022-04-28 09:05:28 +02:00
2022-06-29 09:37:18 +02:00
class BlackBoxWrapper(gym.ObservationWrapper, ABC):
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.
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.
"""
2022-04-28 09:05:28 +02:00
super().__init__()
self.env = env
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
2022-04-28 09:05:28 +02:00
2022-06-29 09:37:18 +02:00
# 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)
2022-06-29 09:37:18 +02:00
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
2022-04-28 09:05:28 +02:00
2022-06-29 09:37:18 +02:00
# spaces
self.mp_action_space = self.get_mp_action_space()
self.action_space = self.get_action_space()
2022-06-29 09:37:18 +02:00
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],
2022-04-28 09:05:28 +02:00
dtype=self.env.observation_space.dtype)
2022-06-29 09:37:18 +02:00
# rendering
self.render_mode = None
self.render_kwargs = {}
self.verbose = verbose
2022-06-29 09:37:18 +02:00
@property
def dt(self):
return self.env.dt
def observation(self, observation):
return observation[self.env.context_mask]
2022-04-28 09:05:28 +02:00
def get_trajectory(self, action: np.ndarray) -> Tuple:
2022-05-05 18:50:20 +02:00
# TODO: this follows the implementation of the mp_pytorch library which includes the parameters tau and delay at
2022-05-03 19:51:54 +02:00
# the beginning of the array.
2022-06-29 09:37:18 +02:00
# 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)
2022-04-28 09:05:28 +02:00
trajectory_tensor, velocity_tensor = traj_dict['pos'], traj_dict['vel']
trajectory = trajectory_tensor.numpy()
velocity = velocity_tensor.numpy()
# TODO: Do we need this or does mp_pytorch have this?
2022-04-28 09:05:28 +02:00
if self.post_traj_steps > 0:
trajectory = np.vstack([trajectory, np.tile(trajectory[-1, :], [self.post_traj_steps, 1])])
2022-06-29 09:37:18 +02:00
velocity = np.vstack([velocity, np.zeros(shape=(self.post_traj_steps, self.trajectory_generator.num_dof))])
2022-04-28 09:05:28 +02:00
return trajectory, velocity
def get_mp_action_space(self):
2022-06-29 09:37:18 +02:00
"""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()
2022-05-05 18:50:20 +02:00
mp_action_space = gym.spaces.Box(low=min_action_bounds.numpy(), high=max_action_bounds.numpy(),
dtype=np.float32)
2022-05-05 18:50:20 +02:00
return mp_action_space
def get_action_space(self):
2022-05-03 19:51:54 +02:00
"""
This function can be used to modify the action space for considering actions which are not learned via motion
primitives. E.g. ball releasing time for the beer pong task. By default, it is the parameter space of the
motion primitive.
Only needs to be overwritten if the action space needs to be modified.
"""
try:
return self.mp_action_space
except AttributeError:
return self.get_mp_action_space()
2022-05-03 19:51:54 +02:00
2022-04-28 09:05:28 +02:00
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
# TODO: Reward Function rather here?
# agent to learn when to release the ball
2022-05-03 19:51:54 +02:00
mp_params, env_spec_params = self._episode_callback(action)
trajectory, velocity = self.get_trajectory(mp_params)
2022-04-28 09:05:28 +02:00
# TODO
# self.time_steps = np.linspace(0, learned_duration, self.traj_steps)
2022-06-29 09:37:18 +02:00
# self.trajectory_generator.set_mp_times(self.time_steps)
2022-04-28 09:05:28 +02:00
trajectory_length = len(trajectory)
2022-06-29 09:37:18 +02:00
rewards = np.zeros(shape=(trajectory_length,))
if self.verbose >= 2:
2022-05-03 19:51:54 +02:00
actions = np.zeros(shape=(trajectory_length,) + self.env.action_space.shape)
2022-04-28 09:05:28 +02:00
observations = np.zeros(shape=(trajectory_length,) + self.env.observation_space.shape,
dtype=self.env.observation_space.dtype)
2022-04-28 09:05:28 +02:00
infos = dict()
2022-06-29 09:37:18 +02:00
done = False
2022-04-28 09:05:28 +02:00
for t, pos_vel in enumerate(zip(trajectory, velocity)):
2022-06-29 09:37:18 +02:00
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
2022-05-03 19:51:54 +02:00
c_action = np.clip(step_action, self.env.action_space.low, self.env.action_space.high)
2022-05-05 18:50:20 +02:00
# print('step/clipped action ratio: ', step_action/c_action)
2022-05-03 19:51:54 +02:00
obs, c_reward, done, info = self.env.step(c_action)
2022-06-29 09:37:18 +02:00
rewards[t] = c_reward
2022-05-03 19:51:54 +02:00
if self.verbose >= 2:
actions[t, :] = c_action
observations[t, :] = obs
2022-06-29 09:37:18 +02:00
2022-04-28 09:05:28 +02:00
for k, v in info.items():
elems = infos.get(k, [None] * trajectory_length)
elems[t] = v
infos[k] = elems
2022-06-29 09:37:18 +02:00
if self.render_mode is not None:
2022-04-28 09:05:28 +02:00
self.render(mode=self.render_mode, **self.render_kwargs)
2022-06-29 09:37:18 +02:00
if done or self.env.do_replanning(self.env.current_pos, self.env.current_vel, obs, c_action, t):
2022-04-28 09:05:28 +02:00
break
2022-06-29 09:37:18 +02:00
2022-04-28 09:05:28 +02:00
infos.update({k: v[:t + 1] for k, v in infos.items()})
2022-06-29 09:37:18 +02:00
2022-05-03 19:51:54 +02:00
if self.verbose >= 2:
infos['trajectory'] = trajectory
2022-05-02 15:06:21 +02:00
infos['step_actions'] = actions[:t + 1]
infos['step_observations'] = observations[:t + 1]
infos['step_rewards'] = rewards[:t + 1]
2022-06-29 09:37:18 +02:00
2022-05-29 11:58:01 +02:00
infos['trajectory_length'] = t + 1
2022-06-29 09:37:18 +02:00
trajectory_return = self.reward_aggregation(rewards[:t + 1])
2022-05-03 19:51:54 +02:00
return self.get_observation_from_step(obs), trajectory_return, done, infos
2022-04-28 09:05:28 +02:00
def reset(self):
return self.get_observation_from_step(self.env.reset())
def render(self, mode='human', **kwargs):
"""Only set render options here, such that they can be used during the rollout.
This only needs to be called once"""
self.render_mode = mode
self.render_kwargs = kwargs
# self.env.render(mode=self.render_mode, **self.render_kwargs)
self.env.render(mode=self.render_mode)
def get_observation_from_step(self, observation: np.ndarray) -> np.ndarray:
return observation[self.active_obs]
2022-05-31 19:41:08 +02:00
def seed(self, seed=None):
self.env.seed(seed)
def plot_trajs(self, des_trajs, des_vels):
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('TkAgg')
pos_fig = plt.figure('positions')
vel_fig = plt.figure('velocities')
for i in range(des_trajs.shape[1]):
plt.figure(pos_fig.number)
plt.subplot(des_trajs.shape[1], 1, i + 1)
plt.plot(np.ones(des_trajs.shape[0]) * self.current_pos[i])
plt.plot(des_trajs[:, i])
plt.figure(vel_fig.number)
plt.subplot(des_vels.shape[1], 1, i + 1)
plt.plot(np.ones(des_trajs.shape[0]) * self.current_vel[i])
plt.plot(des_vels[:, i])