440 lines
17 KiB
Python
Raw Normal View History

2023-10-23 15:45:14 +02:00
from torch_robotics.isaac_gym_envs.motion_planning_envs import PandaMotionPlanningIsaacGymEnv, MotionPlanningController
import os
import pickle
from math import ceil
from pathlib import Path
import einops
import matplotlib.pyplot as plt
import torch
from einops._torch_specific import allow_ops_in_compiled_graph # requires einops>=0.6.1
from experiment_launcher import single_experiment_yaml, run_experiment
from mp_baselines.planners.costs.cost_functions import CostCollision, CostComposite, CostGPTrajectory
from mpd.models import TemporalUnet, UNET_DIM_MULTS
from mpd.models.diffusion_models.guides import GuideManagerTrajectoriesWithVelocity
from mpd.models.diffusion_models.sample_functions import guide_gradient_steps, ddpm_sample_fn
from mpd.trainer import get_dataset, get_model
from mpd.utils.loading import load_params_from_yaml
from torch_robotics.robots import RobotPanda
from torch_robotics.torch_utils.seed import fix_random_seed
from torch_robotics.torch_utils.torch_timer import TimerCUDA
from torch_robotics.torch_utils.torch_utils import get_torch_device, freeze_torch_model_params
from torch_robotics.trajectory.metrics import compute_smoothness, compute_path_length, compute_variance_waypoints
from torch_robotics.trajectory.utils import interpolate_traj_via_points
from torch_robotics.visualizers.planning_visualizer import PlanningVisualizer
allow_ops_in_compiled_graph()
TRAINED_MODELS_DIR = '../../data_trained_models/'
@single_experiment_yaml
def experiment(
########################################################################################################################
# Experiment configuration
# model_id: str = 'EnvDense2D-RobotPointMass',
# model_id: str = 'EnvNarrowPassageDense2D-RobotPointMass',
# model_id: str = 'EnvSimple2D-RobotPointMass',
model_id: str = 'EnvSpheres3D-RobotPanda',
# planner_alg: str = 'diffusion_prior',
# planner_alg: str = 'diffusion_prior_then_guide',
planner_alg: str = 'mpd',
use_guide_on_extra_objects_only: bool = False,
n_samples: int = 50,
start_guide_steps_fraction: float = 0.25,
n_guide_steps: int = 5,
n_diffusion_steps_without_noise: int = 5,
weight_grad_cost_collision: float = 1e-2,
weight_grad_cost_smoothness: float = 1e-7,
factor_num_interpolated_points_for_collision: float = 1.5,
trajectory_duration: float = 5.0, # currently fixed
########################################################################
device: str = 'cuda',
debug: bool = True,
render: bool = True,
########################################################################
# MANDATORY
seed: int = 30,
results_dir: str = 'logs',
########################################################################
**kwargs
):
########################################################################################################################
fix_random_seed(seed)
device = get_torch_device(device)
tensor_args = {'device': device, 'dtype': torch.float32}
########################################################################################################################
print(f'##########################################################################################################')
print(f'Model -- {model_id}')
print(f'Algorithm -- {planner_alg}')
run_prior_only = False
run_prior_then_guidance = False
if planner_alg == 'mpd':
pass
elif planner_alg == 'diffusion_prior_then_guide':
run_prior_then_guidance = True
elif planner_alg == 'diffusion_prior':
run_prior_only = True
else:
raise NotImplementedError
########################################################################################################################
model_dir = os.path.join(TRAINED_MODELS_DIR, model_id)
results_dir = os.path.join(model_dir, 'results_inference', str(seed))
os.makedirs(results_dir, exist_ok=True)
args = load_params_from_yaml(os.path.join(model_dir, "args.yaml"))
########################################################################################################################
# Load dataset with env, robot, task
train_subset, train_dataloader, val_subset, val_dataloader = get_dataset(
dataset_class='TrajectoryDataset',
use_extra_objects=True,
obstacle_cutoff_margin=0.05,
**args,
tensor_args=tensor_args
)
dataset = train_subset.dataset
n_support_points = dataset.n_support_points
env = dataset.env
robot = dataset.robot
task = dataset.task
dt = trajectory_duration / n_support_points # time interval for finite differences
# set robot's dt
robot.dt = dt
########################################################################################################################
# Load prior model
diffusion_configs = dict(
variance_schedule=args['variance_schedule'],
n_diffusion_steps=args['n_diffusion_steps'],
predict_epsilon=args['predict_epsilon'],
)
unet_configs = dict(
state_dim=dataset.state_dim,
n_support_points=dataset.n_support_points,
unet_input_dim=args['unet_input_dim'],
dim_mults=UNET_DIM_MULTS[args['unet_dim_mults_option']],
)
diffusion_model = get_model(
model_class=args['diffusion_model_class'],
model=TemporalUnet(**unet_configs),
tensor_args=tensor_args,
**diffusion_configs,
**unet_configs
)
diffusion_model.load_state_dict(
torch.load(os.path.join(model_dir, 'checkpoints', 'ema_model_current_state_dict.pth' if args['use_ema'] else 'model_current_state_dict.pth'),
map_location=tensor_args['device'])
)
diffusion_model.eval()
model = diffusion_model
freeze_torch_model_params(model)
model = torch.compile(model)
model.warmup(horizon=n_support_points, device=device)
########################################################################################################################
# Random initial and final positions
n_tries = 100
start_state_pos, goal_state_pos = None, None
for _ in range(n_tries):
q_free = task.random_coll_free_q(n_samples=2)
start_state_pos = q_free[0]
goal_state_pos = q_free[1]
if torch.linalg.norm(start_state_pos - goal_state_pos) > dataset.threshold_start_goal_pos:
break
if start_state_pos is None or goal_state_pos is None:
raise ValueError(f"No collision free configuration was found\n"
f"start_state_pos: {start_state_pos}\n"
f"goal_state_pos: {goal_state_pos}\n")
print(f'start_state_pos: {start_state_pos}')
print(f'goal_state_pos: {goal_state_pos}')
########################################################################################################################
# Run motion planning inference
########
# normalize start and goal positions
hard_conds = dataset.get_hard_conditions(torch.vstack((start_state_pos, goal_state_pos)), normalize=True)
context = None
########
# Set up the planning costs
# Cost collisions
cost_collision_l = []
weights_grad_cost_l = [] # for guidance, the weights_cost_l are the gradient multipliers (after gradient clipping)
if use_guide_on_extra_objects_only:
collision_fields = task.get_collision_fields_extra_objects()
else:
collision_fields = task.get_collision_fields()
for collision_field in collision_fields:
cost_collision_l.append(
CostCollision(
robot, n_support_points,
field=collision_field,
sigma_coll=1.0,
tensor_args=tensor_args
)
)
weights_grad_cost_l.append(weight_grad_cost_collision)
# Cost smoothness
cost_smoothness_l = [
CostGPTrajectory(
robot, n_support_points, dt, sigma_gp=1.0,
tensor_args=tensor_args
)
]
weights_grad_cost_l.append(weight_grad_cost_smoothness)
####### Cost composition
cost_func_list = [
*cost_collision_l,
*cost_smoothness_l
]
cost_composite = CostComposite(
robot, n_support_points, cost_func_list,
weights_cost_l=weights_grad_cost_l,
tensor_args=tensor_args
)
########
# Guiding manager
guide = GuideManagerTrajectoriesWithVelocity(
dataset,
cost_composite,
clip_grad=True,
interpolate_trajectories_for_collision=True,
num_interpolated_points=ceil(n_support_points * factor_num_interpolated_points_for_collision),
tensor_args=tensor_args,
)
t_start_guide = ceil(start_guide_steps_fraction * model.n_diffusion_steps)
sample_fn_kwargs = dict(
guide=None if run_prior_then_guidance or run_prior_only else guide,
n_guide_steps=n_guide_steps,
t_start_guide=t_start_guide,
noise_std_extra_schedule_fn=lambda x: 0.5,
)
########
# Sample trajectories with the diffusion/cvae model
with TimerCUDA() as timer_model_sampling:
trajs_normalized_iters = model.run_inference(
context, hard_conds,
n_samples=n_samples, horizon=n_support_points,
return_chain=True,
sample_fn=ddpm_sample_fn,
**sample_fn_kwargs,
n_diffusion_steps_without_noise=n_diffusion_steps_without_noise,
# ddim=True
)
print(f't_model_sampling: {timer_model_sampling.elapsed:.3f} sec')
t_total = timer_model_sampling.elapsed
########
# run extra guiding steps without diffusion
if run_prior_then_guidance:
n_post_diffusion_guide_steps = (t_start_guide + n_diffusion_steps_without_noise) * n_guide_steps
with TimerCUDA() as timer_post_model_sample_guide:
trajs = trajs_normalized_iters[-1]
trajs_post_diff_l = []
for i in range(n_post_diffusion_guide_steps):
trajs = guide_gradient_steps(
trajs,
hard_conds=hard_conds,
guide=guide,
n_guide_steps=1,
unnormalize_data=False
)
trajs_post_diff_l.append(trajs)
chain = torch.stack(trajs_post_diff_l, dim=1)
chain = einops.rearrange(chain, 'b post_diff_guide_steps h d -> post_diff_guide_steps b h d')
trajs_normalized_iters = torch.cat((trajs_normalized_iters, chain))
print(f't_post_diffusion_guide: {timer_post_model_sample_guide.elapsed:.3f} sec')
t_total = timer_model_sampling.elapsed + timer_post_model_sample_guide.elapsed
# unnormalize trajectory samples from the models
trajs_iters = dataset.unnormalize_trajectories(trajs_normalized_iters)
trajs_final = trajs_iters[-1]
trajs_final_coll, trajs_final_coll_idxs, trajs_final_free, trajs_final_free_idxs, _ = task.get_trajs_collision_and_free(trajs_final, return_indices=True)
########################################################################################################################
# Compute motion planning metrics
print(f'\n----------------METRICS----------------')
print(f't_total: {t_total:.3f} sec')
success_free_trajs = task.compute_success_free_trajs(trajs_final)
fraction_free_trajs = task.compute_fraction_free_trajs(trajs_final)
collision_intensity_trajs = task.compute_collision_intensity_trajs(trajs_final)
print(f'success: {success_free_trajs}')
print(f'percentage free trajs: {fraction_free_trajs*100:.2f}')
print(f'percentage collision intensity: {collision_intensity_trajs*100:.2f}')
# compute costs only on collision-free trajectories
traj_final_free_best = None
idx_best_traj = None
cost_best_free_traj = None
cost_smoothness = None
cost_path_length = None
cost_all = None
variance_waypoint_trajs_final_free = None
if trajs_final_free is not None:
cost_smoothness = compute_smoothness(trajs_final_free, robot)
print(f'cost smoothness: {cost_smoothness.mean():.4f}, {cost_smoothness.std():.4f}')
cost_path_length = compute_path_length(trajs_final_free, robot)
print(f'cost path length: {cost_path_length.mean():.4f}, {cost_path_length.std():.4f}')
# compute best trajectory
cost_all = cost_path_length + cost_smoothness
idx_best_traj = torch.argmin(cost_all).item()
traj_final_free_best = trajs_final_free[idx_best_traj]
cost_best_free_traj = torch.min(cost_all).item()
print(f'cost best: {cost_best_free_traj:.3f}')
# variance of waypoints
variance_waypoint_trajs_final_free = compute_variance_waypoints(trajs_final_free, robot)
print(f'variance waypoint: {variance_waypoint_trajs_final_free:.4f}')
print(f'\n--------------------------------------\n')
########################################################################################################################
# Save data
results_data_dict = {
'trajs_iters': trajs_iters,
'trajs_final_coll': trajs_final_coll,
'trajs_final_coll_idxs': trajs_final_coll_idxs,
'trajs_final_free': trajs_final_free,
'trajs_final_free_idxs': trajs_final_free_idxs,
'success_free_trajs': success_free_trajs,
'fraction_free_trajs': fraction_free_trajs,
'collision_intensity_trajs': collision_intensity_trajs,
'idx_best_traj': idx_best_traj,
'traj_final_free_best': traj_final_free_best,
'cost_best_free_traj': cost_best_free_traj,
'cost_path_length_trajs_final_free': cost_smoothness,
'cost_smoothness_trajs_final_free': cost_path_length,
'cost_all_trajs_final_free': cost_all,
'variance_waypoint_trajs_final_free': variance_waypoint_trajs_final_free,
't_total': t_total
}
with open(os.path.join(results_dir, 'results_data_dict.pickle'), 'wb') as handle:
pickle.dump(results_data_dict, handle, protocol=pickle.HIGHEST_PROTOCOL)
########################################################################################################################
# Render results
if render:
# Render
planner_visualizer = PlanningVisualizer(
task=task,
)
base_file_name = Path(os.path.basename(__file__)).stem
pos_trajs_iters = robot.get_position(trajs_iters)
planner_visualizer.animate_opt_iters_joint_space_state(
trajs=trajs_iters,
pos_start_state=start_state_pos, pos_goal_state=goal_state_pos,
vel_start_state=torch.zeros_like(start_state_pos), vel_goal_state=torch.zeros_like(goal_state_pos),
traj_best=traj_final_free_best,
video_filepath=os.path.join(results_dir, f'{base_file_name}-joint-space-opt-iters.mp4'),
n_frames=max((2, len(trajs_iters))),
anim_time=5
)
if isinstance(robot, RobotPanda):
# visualize in Isaac Gym
# POSITION CONTROL
# add initial positions for better visualization
n_first_steps = 10
n_last_steps = 10
trajs_pos = robot.get_position(trajs_final_free).movedim(1, 0)
trajs_vel = robot.get_velocity(trajs_final_free).movedim(1, 0)
trajs_pos = interpolate_traj_via_points(trajs_pos.movedim(0, 1), 2).movedim(1, 0)
motion_planning_isaac_env = PandaMotionPlanningIsaacGymEnv(
env, robot, task,
asset_root="../../deps/isaacgym/assets",
controller_type='position',
num_envs=trajs_pos.shape[1],
all_robots_in_one_env=True,
color_robots=False,
show_goal_configuration=True,
sync_with_real_time=True,
show_collision_spheres=False,
dt=dt,
**results_data_dict,
# show_collision_spheres=True
)
motion_planning_controller = MotionPlanningController(motion_planning_isaac_env)
motion_planning_controller.run_trajectories(
trajs_pos,
start_states_joint_pos=trajs_pos[0], goal_state_joint_pos=trajs_pos[-1][0],
n_first_steps=n_first_steps,
n_last_steps=n_last_steps,
visualize=True,
render_viewer_camera=True,
make_video=True,
video_path=os.path.join(results_dir, f'{base_file_name}-isaac-controller-position.mp4'),
make_gif=False
)
else:
# visualize in the planning environment
planner_visualizer.animate_opt_iters_robots(
trajs=pos_trajs_iters, start_state=start_state_pos, goal_state=goal_state_pos,
traj_best=traj_final_free_best,
video_filepath=os.path.join(results_dir, f'{base_file_name}-traj-opt-iters.mp4'),
n_frames=max((2, len(trajs_iters))),
anim_time=5
)
planner_visualizer.animate_robot_trajectories(
trajs=pos_trajs_iters[-1], start_state=start_state_pos, goal_state=goal_state_pos,
plot_trajs=True,
video_filepath=os.path.join(results_dir, f'{base_file_name}-robot-traj.mp4'),
# n_frames=max((2, pos_trajs_iters[-1].shape[1]//10)),
n_frames=pos_trajs_iters[-1].shape[1],
anim_time=trajectory_duration
)
plt.show()
if __name__ == '__main__':
# Leave unchanged
run_experiment(experiment)