# # Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # NVIDIA CORPORATION, its affiliates and licensors retain all intellectual # property and proprietary rights in and to this material, related # documentation and any modifications thereto. Any use, reproduction, # disclosure or distribution of this material and related documentation # without an express license agreement from NVIDIA CORPORATION or # its affiliates is strictly prohibited. # """ This module contains :meth:`MotionGen` class that provides a high-level interface for motion generation. Motion Generation can take goals either as joint configurations :meth:`MotionGen.plan_single_js` or as Cartesian poses :meth:`MotionGen.plan_single`. When Cartesian pose is given as target, inverse kinematics is first done to generate seeds for trajectory optimization. Motion generation fallback to using a graph planner when linear interpolated trajectory optimization seeds are not successful. Reaching one Cartesian pose in a goalset is also supported using :meth:`MotionGen.plan_goalset`. Batched planning in the same world environment ( :meth:`MotionGen.plan_batch`) and different world environments (:meth:`MotionGen.plan_batch_env`) is also provided. .. raw:: html

A motion generation request can be configured using :meth:`MotionGenPlanConfig`. The result of motion generation is returned as a :meth:`MotionGenResult`. A minimal example is availble at :ref:`python_motion_gen_example`. """ from __future__ import annotations # Standard Library import math import time from dataclasses import dataclass from enum import Enum from typing import Any, Dict, List, Optional, Tuple, Union # Third Party import numpy as np import torch import torch.autograd.profiler as profiler import warp as wp # CuRobo from curobo.cuda_robot_model.cuda_robot_model import CudaRobotModel from curobo.geom.sdf.utils import create_collision_checker from curobo.geom.sdf.world import CollisionCheckerType, WorldCollision, WorldCollisionConfig from curobo.geom.sphere_fit import SphereFitType from curobo.geom.types import Cuboid, Obstacle, WorldConfig from curobo.graph.graph_base import GraphConfig, GraphPlanBase, GraphResult from curobo.graph.prm import PRMStar from curobo.rollout.arm_reacher import ArmReacher from curobo.rollout.cost.pose_cost import PoseCostMetric from curobo.rollout.dynamics_model.kinematic_model import KinematicModelState from curobo.rollout.rollout_base import Goal, RolloutBase, RolloutMetrics from curobo.types.base import TensorDeviceType from curobo.types.camera import CameraObservation from curobo.types.math import Pose from curobo.types.robot import RobotConfig from curobo.types.state import JointState from curobo.types.tensor import T_BDOF, T_DOF, T_BValue_float from curobo.util.logger import log_error, log_info, log_warn from curobo.util.tensor_util import tensor_repeat_seeds from curobo.util.trajectory import InterpolateType, get_batch_interpolated_trajectory from curobo.util.warp import init_warp from curobo.util_file import ( get_robot_configs_path, get_task_configs_path, get_world_configs_path, join_path, load_yaml, ) from curobo.wrap.reacher.evaluator import TrajEvaluator, TrajEvaluatorConfig from curobo.wrap.reacher.ik_solver import IKResult, IKSolver, IKSolverConfig from curobo.wrap.reacher.trajopt import TrajOptResult, TrajOptSolver, TrajOptSolverConfig from curobo.wrap.reacher.types import ReacherSolveState, ReacherSolveType @dataclass class MotionGenConfig: """Configuration dataclass for creating a motion generation instance.""" #: number of IK seeds to run per query problem. ik_seeds: int #: number of graph planning seeds to use per query problem. graph_seeds: int #: number of trajectory optimization seeds to use per query problem. trajopt_seeds: int #: number of seeds to run trajectory optimization per trajopt_seed. noisy_trajopt_seeds: int #: number of IK seeds to use for batched queries. batch_ik_seeds: int #: number of trajectory optimization seeds to use for batched queries. batch_trajopt_seeds: int #: instance of robot configuration shared across all solvers. robot_cfg: RobotConfig #: instance of IK solver to use for motion generation. ik_solver: IKSolver #: instance of graph planner to use. graph_planner: GraphPlanBase #: instance of trajectory optimization solver to use for reaching Cartesian poses. trajopt_solver: TrajOptSolver #: instance of trajectory optimization solver to use for reaching joint space targets. js_trajopt_solver: TrajOptSolver #: instance of trajectory optimization solver for final fine tuning for joint space targets. finetune_js_trajopt_solver: TrajOptSolver #: instance of trajectory optimization solver for final fine tuning. finetune_trajopt_solver: TrajOptSolver #: interpolation to use for getting dense waypoints from optimized solution. interpolation_type: InterpolateType #: maximum number of steps to interpolate interpolation_steps: int #: instance of world collision checker. world_coll_checker: WorldCollision #: device to load motion generation. tensor_args: TensorDeviceType #: number of IK iterations to run for initializing trajectory optimization partial_ik_iters: int #: number of iterations to run trajectory optimization when seeded from a graph plan. graph_trajopt_iters: Optional[int] = None #: store debugging information in MotionGenResult store_debug_in_result: bool = False #: interpolation dt to use for output trajectory. interpolation_dt: float = 0.01 #: scale initial dt by this value to finetune trajectory optimization. finetune_dt_scale: float = 0.9 #: record compute ops as cuda graphs and replay recorded graphs for upto 10x faster execution. use_cuda_graph: bool = True #: After 100 iterations of trajectory optimization, a new dt is computed that pushes the #: velocity, acceleration, or jerk limits to the maximum. This new dt is then used with a #: reduction :attr:`MotionGenPlanConfig.finetune_dt_scale` to run 200+ iterations of trajectory #: optimization. If trajectory optimization fails with the new dt, the new dt is increased and #: tried again until :attr:`MotionGenPlanConfig.finetune_attempts`. optimize_dt: bool = True @staticmethod def load_from_robot_config( robot_cfg: Union[Union[str, Dict], RobotConfig], world_model: Optional[Union[Union[str, Dict], WorldConfig]] = None, tensor_args: TensorDeviceType = TensorDeviceType(), num_ik_seeds: int = 32, num_graph_seeds: int = 4, num_trajopt_seeds: int = 4, num_batch_ik_seeds: int = 32, num_batch_trajopt_seeds: int = 1, num_trajopt_noisy_seeds: int = 1, position_threshold: float = 0.005, rotation_threshold: float = 0.05, cspace_threshold: float = 0.05, world_coll_checker=None, base_cfg_file: str = "base_cfg.yml", particle_ik_file: str = "particle_ik.yml", gradient_ik_file: str = "gradient_ik.yml", graph_file: str = "graph.yml", particle_trajopt_file: str = "particle_trajopt.yml", gradient_trajopt_file: str = "gradient_trajopt.yml", finetune_trajopt_file: Optional[str] = None, trajopt_tsteps: int = 32, interpolation_steps: int = 5000, interpolation_dt: float = 0.02, interpolation_type: InterpolateType = InterpolateType.LINEAR_CUDA, use_cuda_graph: bool = True, self_collision_check: bool = True, self_collision_opt: bool = True, grad_trajopt_iters: Optional[int] = None, trajopt_seed_ratio: Dict[str, int] = {"linear": 1.0, "bias": 0.0}, ik_opt_iters: Optional[int] = None, ik_particle_opt: bool = True, collision_checker_type: Optional[CollisionCheckerType] = CollisionCheckerType.MESH, sync_cuda_time: Optional[bool] = None, trajopt_particle_opt: bool = True, traj_evaluator_config: Optional[TrajEvaluatorConfig] = None, traj_evaluator: Optional[TrajEvaluator] = None, minimize_jerk: bool = True, filter_robot_command: bool = True, use_gradient_descent: bool = False, collision_cache: Optional[Dict[str, int]] = None, n_collision_envs: Optional[int] = None, ee_link_name: Optional[str] = None, use_es_ik: Optional[bool] = None, use_es_trajopt: Optional[bool] = None, es_ik_learning_rate: float = 1.0, es_trajopt_learning_rate: float = 1.0, use_ik_fixed_samples: Optional[bool] = None, use_trajopt_fixed_samples: Optional[bool] = None, evaluate_interpolated_trajectory: bool = True, partial_ik_iters: int = 2, fixed_iters_trajopt: Optional[bool] = None, store_ik_debug: bool = False, store_trajopt_debug: bool = False, graph_trajopt_iters: Optional[int] = None, collision_max_outside_distance: Optional[float] = None, collision_activation_distance: Optional[float] = None, trajopt_dt: Optional[float] = None, js_trajopt_dt: Optional[float] = None, js_trajopt_tsteps: Optional[int] = None, trim_steps: Optional[List[int]] = None, store_debug_in_result: bool = False, finetune_trajopt_iters: Optional[int] = None, smooth_weight: List[float] = None, finetune_smooth_weight: Optional[List[float]] = None, state_finite_difference_mode: Optional[str] = None, finetune_dt_scale: float = 0.9, minimum_trajectory_dt: Optional[float] = None, maximum_trajectory_time: Optional[float] = None, maximum_trajectory_dt: Optional[float] = None, velocity_scale: Optional[Union[List[float], float]] = None, acceleration_scale: Optional[Union[List[float], float]] = None, jerk_scale: Optional[Union[List[float], float]] = None, optimize_dt: bool = True, project_pose_to_goal_frame: bool = True, ik_seed: int = 1531, graph_seed: int = 1531, high_precision: bool = False, ): """Create a motion generation configuration from robot and world configuration. Args: robot_cfg: Robot configuration to use for motion generation. This can be a path to a yaml file, a dictionary, or an instance of :class:`RobotConfig`. See :ref:`available_robot_list` for a list of available robots. You can also create a a configuration file for your robot using :ref:`tut_robot_configuration`. world_model: World configuration to use for motion generation. This can be a path to a yaml file, a dictionary, or an instance of :class:`WorldConfig`. See :ref:`world_collision` for more details. tensor_args: Numerical precision and compute device to use for motion generation. num_ik_seeds: Number of seeds to use for solving inverse kinematics. Default of 32 is found to be a good number for most cases. In sparse environments, a lower number of 16 can also be used. num_graph_seeds: Number of seeds to use for graph planner per problem query. When graph planning is used to generate seeds for trajectory optimization, graph planner will attempt to find collision-free paths from the start state to the many inverse kinematics solutions. num_trajopt_seeds: Number of seeds to use for trajectory optimization per problem query. Default of 4 is found to be a good number for most cases. Increasing this will increase memory usage. num_batch_ik_seeds: Number of seeds to use for inverse kinematics during batched planning. Default of 32 is found to be a good number for most cases. num_batch_trajopt_seeds: Number of seeds to use for trajectory optimization during batched planning. Using more than 1 will disable graph planning for batched planning. num_trajopt_noisy_seeds: Number of augmented trajectories to use per trajectory seed. The augmentation is done by adding random noise to the trajectory. This augmentation has not been found to be useful and it's recommended to keep this to 1. The noisy seeds can also be used in conjunction with the trajopt_seed_ratio to generate seeds that go through a bias point. position_threshold: Position threshold in meters between reached position and target position used to measure success. rotation_threshold: Rotation threshold between reached orientation and target orientation used to measure success. The metric is q^T * q, where q is the quaternion difference between target and reached orientation. The metric is not easy to interpret and a future release will provide a more intuitive metric. For now, use 0.05 as a good value. cspace_threshold: Joint space threshold in radians for revolute joints and meters for linear joints between reached joint configuration and target joint configuration used to measure success. Default of 0.05 has been found to be a good value for most cases. world_coll_checker: Instance of world collision checker to use for motion generation. Leaving this to None will create a new instance of world collision checker using the provided attr:`world_model`. base_cfg_file: Base configuration file containing convergence and constraint criteria to measure success. particle_ik_file: Optimizer configuration file to use for particle-based optimization during inverse kinematics. gradient_ik_file: Optimizer configuration file to use for gradient-based optimization during inverse kinematics. graph_file: Configuration file to use for graph planner. particle_trajopt_file: Optimizer configuration file to use for particle-based optimization during trajectory optimization. gradient_trajopt_file: Optimizer configuration file to use for gradient-based optimization during trajectory optimization. finetune_trajopt_file: Optimizer configuration file to use for finetuning trajectory optimization. trajopt_tsteps: Number of waypoints to use for trajectory optimization. Default of 32 is found to be a good number for most cases. interpolation_steps: Buffer size to use for storing interpolated trajectory. Default of 5000 is found to be a good number for most cases. interpolation_dt: Time step in seconds to use for generating interpolated trajectory from optimized trajectory. Change this if you want to generate a trajectory with a fixed timestep between waypoints. interpolation_type: Interpolation type to use for generating dense waypoints from optimized trajectory. Default of :py:attr:`curobo.util.trajectory.InterpolateType.LINEAR_CUDA` is found to be a good choice for most cases. Other suitable options for real robot execution are :py:attr:`curobo.util.trajectory.InterpolateType.QUINTIC` and :py:attr:`curobo.util.trajectory.InterpolateType.CUBIC`. use_cuda_graph: Record compute ops as cuda graphs and replay recorded graphs where implemented. This can speed up execution by upto 10x. Default of True is recommended. Enabling this will prevent changing problem type or batch size after the first call to the solver. self_collision_check: Enable self collision checks for generated motions. Default of True is recommended. Set this to False to debug planning failures. Setting this to False will also set self_collision_opt to False. self_collision_opt: Enable self collision cost during optimization (IK, TrajOpt). Default of True is recommended. grad_trajopt_iters: Number of iterations to run trajectory optimization. trajopt_seed_ratio: Ratio of linear and bias seeds to use for trajectory optimization. Linear seed will generate a linear interpolated trajectory from start state to IK solutions. Bias seed will add a mid-waypoint through the retract configuration. Default of 1.0 linear and 0.0 bias is recommended. This can be changed to 0.5 linear and 0.5 bias, along with changing trajopt_noisy_seeds to 2. ik_opt_iters: Number of iterations to run inverse kinematics. ik_particle_opt: Enable particle-based optimization during inverse kinematics. Default of True is recommended as particle-based optimization moves the random seeds to a regions of local minima. collision_checker_type: Type of collision checker to use for motion generation. Default of CollisionCheckerType.MESH supports world represented by Cuboids and Meshes. See :ref:`world_collision` for more details. sync_cuda_time: Synchronize with host using :py:func:`torch.cuda.synchronize` before measuring compute time. trajopt_particle_opt: Enable particle-based optimization during trajectory optimization. Default of True is recommended as particle-based optimization moves the interpolated seeds away from bad local minima. traj_evaluator_config: Configuration for trajectory evaluator. Default of None will create a new instance of TrajEvaluatorConfig. After trajectory optimization across many seeds, the best trajectory is selected based on this configuration. This evaluator also checks if the optimized dt is within :py:attr:`curobo.wrap.reacher.evaluator.TrajEvaluatorConfig.max_dt`. This check is needed to measure smoothness of the optimized trajectory as bad trajectories can have very high dt to fit within velocity, acceleration, and jerk limits. traj_evaluator: Instance of trajectory evaluator to use for trajectory optimization. Default of None will create a new instance of TrajEvaluator. In case you want to use a custom evaluator, you can create a child instance of TrajEvaluator and pass it. minimize_jerk: Minimize jerk as regularization during trajectory optimizaiton. filter_robot_command: Filter generated trajectory to remove finite difference artifacts. Default of True is recommended. use_gradient_descent: Use gradient descent instead of L-BFGS for trajectory optimization. collision_cache: Cache of obstacles to create to load obstacles between planning calls. An example: ``{"obb": 10, "mesh": 10}``, to create a cache of 10 cuboids and 10 meshes. n_collision_envs: Number of collision environments to create for batched planning across different environments. Only used for :py:meth:`MotionGen.plan_batch_env` and :py:meth:`MotionGen.plan_batch_env_goalset`. ee_link_name: End effector link/frame to use for reaching Cartesian poses. Default of None will use the end effector link from the robot configuration. This cannot be changed after creating the robot configuration. use_es_ik: Use evolutionary strategy for as the particle-based optimizer for inverse kinematics. Default of None will use MPPI as the optimization algorithm. ES is not recommended as it's unstable and provided optimization parameters were not tuned. use_es_trajopt: Use evolutionary strategy as the particle-based optimizer for trajectory optimization. Default of None will use MPPI as the optimization algorithm. ES is not recommended as it's unstable and provided optimization parameters were not tuned. es_ik_learning_rate: Learning rate to use for evolutionary strategy in IK. es_trajopt_learning_rate: Learning rate to use for evolutionary strategy in TrajOpt. use_ik_fixed_samples: Use fixed samples of noise during particle-based optimization of IK. Default of None will use the setting from the optimizer configuration file (``particle_ik.yml``). use_trajopt_fixed_samples: Use fixed samples of noise during particle-based optimization of trajectory optimization. Default of None will use the setting from the optimizer configuration file (``particle_trajopt.yml``). evaluate_interpolated_trajectory: Evaluate interpolated trajectory after optimization. Default of True is recommended to ensure the optimized trajectory is not passing through very thin obstacles. partial_ik_iters: Number of iterations of L-BFGS to run inverse kinematics when only partial IK is needed. fixed_iters_trajopt: Use fixed number of iterations of L-BFGS for trajectory optimization. Default of None will use the setting from the optimizer configuration. In most cases, fixed iterations of solvers are run as current solvers treat constraints as costs and there is no guarantee that the constraints will be satisfied. Instead of checking constraints between iterations of a solver and exiting, it's computationally cheaper to run a fixed number of iterations. In addition, running fixed iterations of solvers is more robust to outlier problems. store_ik_debug: Store debugging information such as values of optimization variables at every iteration in IK result. Setting this to True will set :attr:`use_cuda_graph` to False. store_trajopt_debug: Store debugging information such as values of optimization variables in TrajOpt result. Setting this to True will set :attr:`use_cuda_graph` to False. graph_trajopt_iters: Number of iterations to run trajectory optimization when seeded from a graph plan. Default of None will use the same number of iterations as linear seeded trajectory optimization. This can be set to a higher value of 200 in case where trajectories obtained are not of requird quality. collision_max_outside_distance: Maximum distance to check for collision outside a obstacle. Increasing this value will slow down collision checks with Meshes as closest point queries will be run up to this distance outside an obstacle. collision_activation_distance: Distance in meters to activate collision cost. A good value to start with is 0.01 meters. Increase the distance if the robot needs to stay further away from obstacles. trajopt_dt: Time step in seconds to use for trajectory optimization. A good value to start with is 0.15 seconds. This value is used to compute velocity, acceleration, and jerk values for waypoints through finite difference. js_trajopt_dt: Time step in seconds to use for trajectory optimization when reaching joint space targets. A good value to start with is 0.15 seconds. This value is used to compute velocity, acceleration, and jerk values for waypoints through finite difference. js_trajopt_tsteps: Number of waypoints to use for trajectory optimization when reaching joint space targets. Default of None will use the same number of waypoints as Cartesian trajectory optimization. trim_steps: Trim waypoints from optimized trajectory. The optimized trajectory will contain the start state at index 0 and have the last two waypoints be the same as T-2 as trajectory optimization implicitly optimizes for zero acceleration and velocity at the last waypoint. An example: ``[1,-2]`` will trim the first waypoint and last 3 waypoints from the optimized trajectory. store_debug_in_result: Store debugging information in MotionGenResult. This value is set to True if either store_ik_debug or store_trajopt_debug is set to True. finetune_trajopt_iters: Number of iterations to run trajectory optimization for finetuning after an initial collision-free trajectory is obtained. smooth_weight: Override smooth weight for trajectory optimization. It's not recommended to set this value for most cases. finetune_smooth_weight: Override smooth weight for finetuning trajectory optimization. It's not recommended to set this value for most cases. state_finite_difference_mode: Finite difference mode to use for computing velocity, acceleration, and jerk values. Default of None will use the setting from the optimizer configuration file. The default finite difference method is a five point stencil to compute the derivatives as this is accurate and provides faster convergence compared to backward or central difference methods. finetune_dt_scale: Scale initial estimated dt by this value to finetune trajectory optimization. This is deprecated and will be removed in future releases. Use :py:attr:`MotionGenPlanConfig.finetune_dt_scale` instead. minimum_trajectory_dt: Minimum time step in seconds allowed for trajectory optimization. maximum_trajectory_time: Maximum time in seconds allowed for trajectory optimization. maximum_trajectory_dt: Maximum time step in seconds allowed for trajectory optimization. velocity_scale: Scale velocity limits by this value. Default of None will not scale the velocity limits. To generate slower trajectories, use :py:attr:`MotionGenPlanConfig.time_dilation_factor` < 1.0 instead. Changing this value is not recommended as it changes the scale of cost terms and they would require retuning. acceleration_scale: Scale acceleration limits by this value. Default of None will not scale the acceleration limits. To generate slower trajectories, use :py:attr:`MotionGenPlanConfig.time_dilation_factor` < 1.0 instead. Changing this value is not recommended as it changes the scale of cost terms and they would require retuning. jerk_scale: Scale jerk limits by this value. Default of None will not scale the jerk limits. To generate slower trajectories, use :py:attr:`MotionGenPlanConfig.time_dilation_factor` < 1.0 instead. Changing this value is not recommended as it changes the scale of cost terms and they would require retuning. optimize_dt: Optimize dt during trajectory optimization. Default of True is recommended to find time-optimal trajectories. Setting this to False will use the provided :attr:`trajopt_dt` for trajectory optimization. Setting to False is required when optimizing from a non-static start state. project_pose_to_goal_frame: Project pose to goal frame when calculating distance between reached and goal pose. Use this to constrain motion to specific axes either in the global frame or the goal frame. ik_seed: Random seed to use for inverse kinematics. graph_seed: Random seed to use for graph planner. high_precision: Use high precision settings for motion generation. This will increase the number of iterations for optimization solvers and reduce the thresholds for position to 1mm and rotation to 0.025. Default of False is recommended for most cases as standard motion generation settings reach within 0.5mm on most problems. Returns: MotionGenConfig: Instance of motion generation configuration. """ if position_threshold <= 0.001: high_precision = True if high_precision: finetune_trajopt_iters = ( 300 if finetune_trajopt_iters is None else max(300, finetune_trajopt_iters) ) if grad_trajopt_iters is None: grad_trajopt_iters = 200 grad_trajopt_iters = max(200, grad_trajopt_iters) position_threshold = min(position_threshold, 0.001) rotation_threshold = min(rotation_threshold, 0.025) cspace_threshold = min(cspace_threshold, 0.01) init_warp(tensor_args=tensor_args) if js_trajopt_tsteps is not None: log_warn("js_trajopt_tsteps is deprecated, use trajopt_tsteps instead.") trajopt_tsteps = js_trajopt_tsteps if trajopt_tsteps is not None: js_trajopt_tsteps = trajopt_tsteps if velocity_scale is not None and isinstance(velocity_scale, float): log_warn( "To slow down trajectories, use MotionGenPlanConfig.time_dilation_factor" + " instead of velocity_scale" ) velocity_scale = [velocity_scale] if acceleration_scale is not None and isinstance(acceleration_scale, float): log_warn( "To slow down trajectories, use MotionGenPlanConfig.time_dilation_factor" + " instead of acceleration_scale" ) acceleration_scale = [acceleration_scale] if jerk_scale is not None and isinstance(jerk_scale, float): jerk_scale = [jerk_scale] if store_ik_debug or store_trajopt_debug: store_debug_in_result = True if ( velocity_scale is not None and min(velocity_scale) < 0.1 and finetune_trajopt_file is None and maximum_trajectory_dt is None ): log_error( "velocity scale<0.1 requires a user determined maximum_trajectory_dt as" + " default scaling will likely fail. A good value to start with would be 30" + " seconds" ) if maximum_trajectory_dt is None: maximum_trajectory_dt = 0.15 maximum_trajectory_dt_acc = maximum_trajectory_dt maximum_trajectory_dt_vel = maximum_trajectory_dt if ( acceleration_scale is not None and min(acceleration_scale) < 1.0 and maximum_trajectory_dt <= 0.2 ): maximum_trajectory_dt_acc = ( np.sqrt(1.0 / min(acceleration_scale)) * maximum_trajectory_dt * 3 ) if ( velocity_scale is not None and min(velocity_scale) < 1.0 and maximum_trajectory_dt <= 0.2 ): maximum_trajectory_dt_vel = (1.0 / min(velocity_scale)) * maximum_trajectory_dt * 3 maximum_trajectory_dt = max(maximum_trajectory_dt_acc, maximum_trajectory_dt_vel) if maximum_trajectory_dt is not None: if trajopt_dt is None: trajopt_dt = maximum_trajectory_dt if js_trajopt_dt is None: js_trajopt_dt = maximum_trajectory_dt if acceleration_scale is not None and min(acceleration_scale) < 0.5: fixed_iters_trajopt = True if velocity_scale is not None and min(velocity_scale) < 0.5: fixed_iters_trajopt = True if ( velocity_scale is not None and min(velocity_scale) <= 0.25 and finetune_trajopt_file is None ): finetune_trajopt_file = "finetune_trajopt_slow.yml" if isinstance(robot_cfg, str): robot_cfg = load_yaml(join_path(get_robot_configs_path(), robot_cfg))["robot_cfg"] elif isinstance(robot_cfg, Dict) and "robot_cfg" in robot_cfg.keys(): robot_cfg = robot_cfg["robot_cfg"] if isinstance(robot_cfg, RobotConfig): if ( ee_link_name is not None and robot_cfg.kinematics.kinematics_config.ee_link != ee_link_name ): log_error("ee link cannot be changed after creating RobotConfig") if ( acceleration_scale is not None and torch.max(robot_cfg.kinematics.kinematics_config.cspace.acceleration_scale) != acceleration_scale ): log_error("acceleration_scale cannot be changed after creating RobotConfig") if ( velocity_scale is not None and torch.max(robot_cfg.kinematics.kinematics_config.cspace.velocity_scale) != velocity_scale ): log_error("velocity cannot be changed after creating RobotConfig") else: if ee_link_name is not None: robot_cfg["kinematics"]["ee_link"] = ee_link_name if jerk_scale is not None: robot_cfg["kinematics"]["cspace"]["jerk_scale"] = jerk_scale if acceleration_scale is not None: robot_cfg["kinematics"]["cspace"]["acceleration_scale"] = acceleration_scale if velocity_scale is not None: robot_cfg["kinematics"]["cspace"]["velocity_scale"] = velocity_scale robot_cfg = RobotConfig.from_dict(robot_cfg, tensor_args) if minimum_trajectory_dt is None: minimum_trajectory_dt = interpolation_dt elif minimum_trajectory_dt < interpolation_dt: log_error("minimum_trajectory_dt cannot be lower than interpolation_dt") if traj_evaluator_config is None: if maximum_trajectory_dt is not None: max_dt = maximum_trajectory_dt if maximum_trajectory_time is not None: max_dt = maximum_trajectory_time / trajopt_tsteps if acceleration_scale is not None: max_dt = max_dt * (1.0 / np.sqrt(min(acceleration_scale))) traj_evaluator_config = TrajEvaluatorConfig.from_basic( min_dt=minimum_trajectory_dt, max_dt=max_dt, dof=robot_cfg.kinematics.dof ) traj_evaluator_config.max_acc = robot_cfg.kinematics.get_joint_limits().acceleration[1] traj_evaluator_config.max_jerk = robot_cfg.kinematics.get_joint_limits().jerk[1] if isinstance(world_model, str): world_model = load_yaml(join_path(get_world_configs_path(), world_model)) base_config_data = load_yaml(join_path(get_task_configs_path(), base_cfg_file)) if collision_cache is not None: base_config_data["world_collision_checker_cfg"]["cache"] = collision_cache if n_collision_envs is not None: base_config_data["world_collision_checker_cfg"]["n_envs"] = n_collision_envs if collision_max_outside_distance is not None: if collision_max_outside_distance < 0.0: log_error("collision_max_outside_distance cannot be negative") base_config_data["world_collision_checker_cfg"][ "max_distance" ] = collision_max_outside_distance if collision_checker_type is not None: # log_info("updating collision checker type to ",collision_checker_type) base_config_data["world_collision_checker_cfg"]["checker_type"] = collision_checker_type if not self_collision_check: base_config_data["constraint"]["self_collision_cfg"]["weight"] = 0.0 if world_coll_checker is None and world_model is not None: world_cfg = WorldCollisionConfig.load_from_dict( base_config_data["world_collision_checker_cfg"], world_model, tensor_args ) world_coll_checker = create_collision_checker(world_cfg) ik_solver_cfg = IKSolverConfig.load_from_robot_config( robot_cfg, world_model, tensor_args, num_ik_seeds, position_threshold, rotation_threshold, world_coll_checker, base_config_data, particle_ik_file, gradient_ik_file, use_cuda_graph=use_cuda_graph, self_collision_check=self_collision_check, self_collision_opt=self_collision_opt, grad_iters=ik_opt_iters, use_particle_opt=ik_particle_opt, sync_cuda_time=sync_cuda_time, use_es=use_es_ik, es_learning_rate=es_ik_learning_rate, use_fixed_samples=use_ik_fixed_samples, store_debug=store_ik_debug, collision_activation_distance=collision_activation_distance, project_pose_to_goal_frame=project_pose_to_goal_frame, seed=ik_seed, high_precision=high_precision, ) ik_solver = IKSolver(ik_solver_cfg) graph_cfg = GraphConfig.load_from_robot_config( robot_cfg, world_model, tensor_args, world_coll_checker, base_config_data, graph_file, use_cuda_graph=use_cuda_graph, seed=graph_seed, ) graph_cfg.interpolation_dt = interpolation_dt graph_cfg.interpolation_steps = interpolation_steps graph_planner = PRMStar(graph_cfg) trajopt_cfg = TrajOptSolverConfig.load_from_robot_config( robot_cfg=robot_cfg, world_model=world_model, tensor_args=tensor_args, position_threshold=position_threshold, rotation_threshold=rotation_threshold, world_coll_checker=world_coll_checker, base_cfg_file=base_config_data, particle_file=particle_trajopt_file, gradient_file=gradient_trajopt_file, traj_tsteps=trajopt_tsteps, interpolation_type=interpolation_type, interpolation_steps=interpolation_steps, use_cuda_graph=use_cuda_graph, self_collision_check=self_collision_check, self_collision_opt=self_collision_opt, grad_trajopt_iters=grad_trajopt_iters, num_seeds=num_trajopt_noisy_seeds, seed_ratio=trajopt_seed_ratio, interpolation_dt=interpolation_dt, use_particle_opt=trajopt_particle_opt, traj_evaluator_config=traj_evaluator_config, traj_evaluator=traj_evaluator, use_gradient_descent=use_gradient_descent, use_es=use_es_trajopt, es_learning_rate=es_trajopt_learning_rate, use_fixed_samples=use_trajopt_fixed_samples, evaluate_interpolated_trajectory=evaluate_interpolated_trajectory, fixed_iters=fixed_iters_trajopt, store_debug=store_trajopt_debug, collision_activation_distance=collision_activation_distance, trajopt_dt=trajopt_dt, store_debug_in_result=store_debug_in_result, smooth_weight=smooth_weight, cspace_threshold=cspace_threshold, state_finite_difference_mode=state_finite_difference_mode, filter_robot_command=filter_robot_command, minimize_jerk=minimize_jerk, optimize_dt=optimize_dt, project_pose_to_goal_frame=project_pose_to_goal_frame, ) trajopt_solver = TrajOptSolver(trajopt_cfg) js_trajopt_cfg = TrajOptSolverConfig.load_from_robot_config( robot_cfg=robot_cfg, world_model=world_model, tensor_args=tensor_args, position_threshold=position_threshold, rotation_threshold=rotation_threshold, world_coll_checker=world_coll_checker, base_cfg_file=base_config_data, particle_file=particle_trajopt_file, gradient_file=gradient_trajopt_file, traj_tsteps=js_trajopt_tsteps, interpolation_type=interpolation_type, interpolation_steps=interpolation_steps, use_cuda_graph=use_cuda_graph, self_collision_check=self_collision_check, self_collision_opt=self_collision_opt, grad_trajopt_iters=grad_trajopt_iters, interpolation_dt=interpolation_dt, use_particle_opt=trajopt_particle_opt, traj_evaluator_config=traj_evaluator_config, traj_evaluator=traj_evaluator, use_gradient_descent=use_gradient_descent, use_es=use_es_trajopt, es_learning_rate=es_trajopt_learning_rate, use_fixed_samples=use_trajopt_fixed_samples, evaluate_interpolated_trajectory=evaluate_interpolated_trajectory, fixed_iters=fixed_iters_trajopt, store_debug=store_trajopt_debug, collision_activation_distance=collision_activation_distance, trajopt_dt=js_trajopt_dt, store_debug_in_result=store_debug_in_result, smooth_weight=smooth_weight, cspace_threshold=cspace_threshold, state_finite_difference_mode=state_finite_difference_mode, minimize_jerk=minimize_jerk, filter_robot_command=filter_robot_command, optimize_dt=optimize_dt, num_seeds=num_trajopt_noisy_seeds, ) js_trajopt_solver = TrajOptSolver(js_trajopt_cfg) if finetune_trajopt_file is None: finetune_trajopt_file = "finetune_trajopt.yml" finetune_trajopt_cfg = TrajOptSolverConfig.load_from_robot_config( robot_cfg=robot_cfg, world_model=world_model, tensor_args=tensor_args, position_threshold=position_threshold, rotation_threshold=rotation_threshold, world_coll_checker=world_coll_checker, base_cfg_file=base_config_data, particle_file=particle_trajopt_file, gradient_file=finetune_trajopt_file, traj_tsteps=trajopt_tsteps, interpolation_type=interpolation_type, interpolation_steps=interpolation_steps, use_cuda_graph=use_cuda_graph, self_collision_check=self_collision_check, self_collision_opt=self_collision_opt, grad_trajopt_iters=finetune_trajopt_iters, interpolation_dt=interpolation_dt, use_particle_opt=False, traj_evaluator_config=traj_evaluator_config, traj_evaluator=traj_evaluator, evaluate_interpolated_trajectory=evaluate_interpolated_trajectory, fixed_iters=fixed_iters_trajopt, store_debug=store_trajopt_debug, collision_activation_distance=collision_activation_distance, trajopt_dt=trajopt_dt, store_debug_in_result=store_debug_in_result, smooth_weight=finetune_smooth_weight, cspace_threshold=cspace_threshold, state_finite_difference_mode=state_finite_difference_mode, minimize_jerk=minimize_jerk, trim_steps=trim_steps, use_gradient_descent=use_gradient_descent, filter_robot_command=filter_robot_command, optimize_dt=optimize_dt, project_pose_to_goal_frame=project_pose_to_goal_frame, ) finetune_trajopt_solver = TrajOptSolver(finetune_trajopt_cfg) finetune_js_trajopt_cfg = TrajOptSolverConfig.load_from_robot_config( robot_cfg=robot_cfg, world_model=world_model, tensor_args=tensor_args, position_threshold=position_threshold, rotation_threshold=rotation_threshold, world_coll_checker=world_coll_checker, base_cfg_file=base_config_data, particle_file=particle_trajopt_file, gradient_file=finetune_trajopt_file, traj_tsteps=js_trajopt_tsteps, interpolation_type=interpolation_type, interpolation_steps=interpolation_steps, use_cuda_graph=use_cuda_graph, self_collision_check=self_collision_check, self_collision_opt=self_collision_opt, grad_trajopt_iters=grad_trajopt_iters, interpolation_dt=interpolation_dt, use_particle_opt=False, traj_evaluator_config=traj_evaluator_config, traj_evaluator=traj_evaluator, use_gradient_descent=use_gradient_descent, use_es=use_es_trajopt, es_learning_rate=es_trajopt_learning_rate, use_fixed_samples=use_trajopt_fixed_samples, evaluate_interpolated_trajectory=evaluate_interpolated_trajectory, fixed_iters=fixed_iters_trajopt, store_debug=store_trajopt_debug, collision_activation_distance=collision_activation_distance, trajopt_dt=js_trajopt_dt, store_debug_in_result=store_debug_in_result, smooth_weight=smooth_weight, cspace_threshold=cspace_threshold, state_finite_difference_mode=state_finite_difference_mode, minimize_jerk=minimize_jerk, filter_robot_command=filter_robot_command, optimize_dt=optimize_dt, num_seeds=num_trajopt_noisy_seeds, ) finetune_js_trajopt_solver = TrajOptSolver(finetune_js_trajopt_cfg) if graph_trajopt_iters is not None: graph_trajopt_iters = math.ceil( graph_trajopt_iters / finetune_trajopt_solver.solver.newton_optimizer.inner_iters ) else: graph_trajopt_iters = finetune_trajopt_solver.solver.newton_optimizer.outer_iters + 2 return MotionGenConfig( num_ik_seeds, num_graph_seeds, num_trajopt_seeds, num_trajopt_noisy_seeds, num_batch_ik_seeds, num_batch_trajopt_seeds, robot_cfg, ik_solver, graph_planner, trajopt_solver=trajopt_solver, js_trajopt_solver=js_trajopt_solver, finetune_js_trajopt_solver=finetune_js_trajopt_solver, finetune_trajopt_solver=finetune_trajopt_solver, interpolation_type=interpolation_type, interpolation_steps=interpolation_steps, world_coll_checker=world_coll_checker, tensor_args=tensor_args, partial_ik_iters=partial_ik_iters, graph_trajopt_iters=graph_trajopt_iters, store_debug_in_result=store_debug_in_result, interpolation_dt=interpolation_dt, finetune_dt_scale=finetune_dt_scale, use_cuda_graph=use_cuda_graph, optimize_dt=optimize_dt, ) @dataclass class MotionGenPlanConfig: """Configuration for querying motion generation.""" #: Use graph planner to generate collision-free seed for trajectory optimization. enable_graph: bool = False #: Enable trajectory optimization. enable_opt: bool = True #: Use neural network IK seed for solving inverse kinematics. Not implemented. use_nn_ik_seed: bool = False #: Run trajectory optimization only if graph planner is successful. need_graph_success: bool = False #: Maximum number of attempts allowed to solve the motion generation problem. max_attempts: int = 60 #: Maximum time in seconds allowed to solve the motion generation problem. timeout: float = 10.0 #: Number of failed attempts at which to fallback to a graph planner for obtaining trajectory #: seeds. enable_graph_attempt: Optional[int] = 3 #: Number of failed attempts at which to disable graph planner. This has not been found to be #: useful. disable_graph_attempt: Optional[int] = None #: Number of IK attempts allowed before returning a failure. Set this to a low value (5) to #: save compute time when an unreachable goal is given. ik_fail_return: Optional[int] = None #: Full IK solving takes 10% of the planning time. Setting this to True will run only 50 #: iterations of IK instead of 100 and then run trajecrtory optimization without checking if #: IK is successful. This can reduce the planning time by 5% but generated solutions can #: have larger motion time and path length. Leave this to False for most cases. partial_ik_opt: bool = False #: Number of seeds to use for solving inverse kinematics. Chanigng this will cause the existing #: CUDAGraphs to be invalidated. Instead, set num_ik_seeds when creating #: :meth:`MotionGenConfig`. num_ik_seeds: Optional[int] = None #: Number of seeds to use for graph planner. We found 4 to be a good number for most cases. The #: default value is set when creating :meth:`MotionGenConfig` so leave this to None. num_graph_seeds: Optional[int] = None #: Number of seeds to use for trajectory optimization. We found 12 to be a good number for most #: cases. The default value is set when creating :meth:`MotionGenConfig` so leave this to None. num_trajopt_seeds: Optional[int] = None #: Ratio of successful motion generation queries to total queries in batch planning mode. Motion #: generation is queries upto :attr:`MotionGenPlanConfig.max_attempts` until this ratio is met. success_ratio: float = 1 #: Return a failure if the query is invalid. Set this to False to debug a query that is invalid. fail_on_invalid_query: bool = True #: use start config as regularization for IK instead of #: :meth:`curobo.types.robot.RobotConfig.kinematics.kinematics_config.retract_config` use_start_state_as_retract: bool = True #: Use a custom pose cost metric for trajectory optimization. This is useful for adding #: additional constraints to motion generation, such as constraining the end-effector's motion #: to a plane or a line or hold orientation while moving. This is also useful for only reaching #: partial pose (e.g., only position). See :meth:`curobo.rollout.cost.pose_cost.PoseCostMetric` #: for more details. pose_cost_metric: Optional[PoseCostMetric] = None #: Run finetuning trajectory optimization after running 100 iterations of #: trajectory optimization. This will provide shorter and smoother trajectories. When #: :attr:`MotionGenConfig.optimize_dt` is True, this flag will also scale the trajectory #: optimization by a new dt. Leave this to True for most cases. If you are not interested in #: finding time-optimal solutions and only want to use motion generation as a feasibility check, #: set this to False. Note that when set to False, the resulting trajectory is only guaranteed #: to be collision-free and within joint limits. When False, it's not guaranteed to be smooth #: and might not execute on a real robot. enable_finetune_trajopt: bool = True #: Run finetuning trajectory optimization across all trajectory optimization seeds. If this is #: set to False, then only 1 successful seed per query is selected and finetuned. When False, #: we have observed that the resulting trajectory is not as optimal as when set to True. parallel_finetune: bool = True #: Scale dt by this value before running finetuning trajectory optimization. This enables #: trajectory optimization to find shorter paths and also account for smoothness over variable #: length trajectories. This is only used when :attr:`MotionGenConfig.optimize_dt` is True. finetune_dt_scale: Optional[float] = 0.85 #: Number of attempts to run finetuning trajectory optimization. Every attempt will increase the #: :attr:`MotionGenPlanConfig.finetune_dt_scale` by #: :attr:`MotionGenPlanConfig.finetune_dt_decay` as a path couldn't be found with the previous #: smaller dt. finetune_attempts: int = 5 #: Decay factor used to increase :attr:`MotionGenPlanConfig.finetune_dt_scale` when optimization #: fails to find a solution. This is only used when :attr:`MotionGenConfig.optimize_dt` is True. finetune_dt_decay: float = 1.025 #: Slow down optimized trajectory by re-timing with a dilation factor. This is useful to #: execute trajectories at a slower speed for debugging. Use this to generate slower #: trajectories instead of reducing :attr:`MotionGenConfig.velocity_scale` or #: :attr:`MotionGenConfig.acceleration_scale` as those parameters will require re-tuning #: of the cost terms while :attr:`MotionGenPlanConfig.time_dilation_factor` will only #: post-process the trajectory. time_dilation_factor: Optional[float] = None #: Check if the start state is valid before runnning any steps in motion generation. This will #: check for joint limits, self-collision, and collision with the world. check_start_validity: bool = True #: Finetune dt scale for joint space planning. finetune_js_dt_scale: Optional[float] = 1.1 def __post_init__(self): """Post initialization checks.""" if not self.enable_opt and not self.enable_graph: log_error("Graph search and Optimization are both disabled, enable one") def clone(self) -> MotionGenPlanConfig: """Clone the current planning configuration.""" return MotionGenPlanConfig( enable_graph=self.enable_graph, enable_opt=self.enable_opt, use_nn_ik_seed=self.use_nn_ik_seed, need_graph_success=self.need_graph_success, max_attempts=self.max_attempts, timeout=self.timeout, enable_graph_attempt=self.enable_graph_attempt, disable_graph_attempt=self.disable_graph_attempt, ik_fail_return=self.ik_fail_return, partial_ik_opt=self.partial_ik_opt, num_ik_seeds=self.num_ik_seeds, num_graph_seeds=self.num_graph_seeds, num_trajopt_seeds=self.num_trajopt_seeds, success_ratio=self.success_ratio, fail_on_invalid_query=self.fail_on_invalid_query, enable_finetune_trajopt=self.enable_finetune_trajopt, parallel_finetune=self.parallel_finetune, use_start_state_as_retract=self.use_start_state_as_retract, pose_cost_metric=( None if self.pose_cost_metric is None else self.pose_cost_metric.clone() ), finetune_dt_scale=self.finetune_dt_scale, finetune_attempts=self.finetune_attempts, time_dilation_factor=self.time_dilation_factor, finetune_js_dt_scale=self.finetune_js_dt_scale, ) class MotionGenStatus(Enum): """Status of motion generation query.""" #: Inverse kinematics failed to find a solution. IK_FAIL = "IK Fail" #: Graph planner failed to find a solution. GRAPH_FAIL = "Graph Fail" #: Trajectory optimization failed to find a solution. TRAJOPT_FAIL = "TrajOpt Fail" #: Finetune Trajectory optimization failed to find a solution. FINETUNE_TRAJOPT_FAIL = "Finetune TrajOpt Fail" #: Invalid query was given. The start state is either out of joint limits, in collision with #: world, or in self-collision. In the future, this will also check for reachability of goal #: pose/ joint target in joint limits. INVALID_QUERY = "Invalid Query" #: Invalid start state was given. Unknown reason. INVALID_START_STATE_UNKNOWN_ISSUE = "Invalid Start State, unknown issue" #: Invalid start state was given. The start state is in world collision. INVALID_START_STATE_WORLD_COLLISION = "Start state is colliding with world" #: Invalid start state was given. The start state is in self-collision. INVALID_START_STATE_SELF_COLLISION = "Start state is in self-collision" #: Invalid start state was given. The start state is out of joint limits. INVALID_START_STATE_JOINT_LIMITS = "Start state is out of joint limits" #: Motion generation query was successful. SUCCESS = "Success" #: Motion generation was not attempted. NOT_ATTEMPTED = "Not Attempted" @dataclass class MotionGenResult: """Result obtained from motion generation.""" #: success tensor with index referring to the batch index. success: Optional[torch.Tensor] = None #: returns true if the start state is not satisfying constraints (e.g., in collision) valid_query: bool = True #: optimized trajectory optimized_plan: Optional[JointState] = None #: dt between steps in the optimized plan optimized_dt: Optional[T_BValue_float] = None #: Cartesian position error at final timestep, returning l2 distance. position_error: Optional[T_BValue_float] = None #: Cartesian rotation error at final timestep, computed as q^(-1) * q_g rotation_error: Optional[T_BValue_float] = None #: Error in joint configuration, when planning in joint space, returning l2 distance. cspace_error: Optional[T_BValue_float] = None #: seconds taken in the optimizer for solving the motion generation problem. solve_time: float = 0.0 #: seconds taken to solve IK. ik_time: float = 0.0 #: seconds taken to find graph plan. graph_time: float = 0.0 #: seconds taken in trajectory optimization. trajopt_time: float = 0.0 #: seconds to run finetune trajectory optimization. finetune_time: float = 0.0 #: sum of ik_time, graph_time, and trajopt_time. This also includes any processing between #: calling the different modules. total_time: float = 0.0 #: interpolated solution, useful for visualization. interpolated_plan: Optional[JointState] = None #: dt between steps in interpolated plan interpolation_dt: float = 0.02 #: last timestep in interpolated plan per batch index. Only used for batched queries path_buffer_last_tstep: Optional[List[int]] = None #: Debug information debug_info: Any = None #: status of motion generation query. status: Optional[Union[MotionGenStatus, str]] = None #: number of attempts used before returning a solution. attempts: int = 1 #: number of trajectory optimization attempts used per attempt. trajopt_attempts: int = 0 #: returns true when a graph plan was used to seed trajectory optimization. used_graph: bool = False #: stores graph plan. graph_plan: Optional[JointState] = None #: stores the index of the goal pose reached when planning for a goalset. goalset_index: Optional[torch.Tensor] = None def clone(self): """Clone the current result.""" m = MotionGenResult( self.success.clone(), valid_query=self.valid_query, optimized_plan=self.optimized_plan.clone() if self.optimized_plan is not None else None, optimized_dt=self.optimized_dt.clone() if self.optimized_dt is not None else None, position_error=self.position_error.clone() if self.position_error is not None else None, rotation_error=self.rotation_error.clone() if self.rotation_error is not None else None, cspace_error=self.cspace_error.clone() if self.cspace_error is not None else None, solve_time=self.solve_time, ik_time=self.ik_time, graph_time=self.graph_time, trajopt_time=self.trajopt_time, total_time=self.total_time, graph_plan=self.graph_plan.clone() if self.graph_plan is not None else None, debug_info=self.debug_info, status=self.status, attempts=self.attempts, trajopt_attempts=self.trajopt_attempts, used_graph=self.used_graph, path_buffer_last_tstep=self.path_buffer_last_tstep, interpolated_plan=( self.interpolated_plan.clone() if self.interpolated_plan is not None else None ), interpolation_dt=self.interpolation_dt, goalset_index=self.goalset_index.clone() if self.goalset_index is not None else None, ) return m def copy_idx(self, idx: torch.Tensor, source_result: MotionGenResult): """Copy data from source result to current result at index. Args: idx: index to copy data at. source_result: source result to copy data from. Returns: MotionGenResult: copied result. """ idx = idx.to(dtype=torch.long) # copy data from source result: self.success[idx] = source_result.success[idx] self.optimized_plan = self._check_none_and_copy_idx( self.optimized_plan, source_result.optimized_plan, idx ) self.interpolated_plan = self._check_none_and_copy_idx( self.interpolated_plan, source_result.interpolated_plan, idx ) self.position_error = self._check_none_and_copy_idx( self.position_error, source_result.position_error, idx ) self.rotation_error = self._check_none_and_copy_idx( self.rotation_error, source_result.rotation_error, idx ) self.cspace_error = self._check_none_and_copy_idx( self.cspace_error, source_result.cspace_error, idx ) self.goalset_index = self._check_none_and_copy_idx( self.goalset_index, source_result.goalset_index, idx ) # NOTE: graph plan will have different shape based on success. # self.graph_plan = self._check_none_and_copy_idx( # self.graph_plan, source_result.graph_plan, idx # ) idx_list = idx.cpu().tolist() if source_result.path_buffer_last_tstep is not None: if self.path_buffer_last_tstep is None: self.path_buffer_last_tstep = [0 for i in range(len(self.success))] for i in idx_list: self.path_buffer_last_tstep[i] = source_result.path_buffer_last_tstep[i] return self def get_paths(self) -> List[JointState]: """Get interpolated trajectories from the result. Use for batched queries. This will return unsuccessful trajectories as well. Use :meth:`MotionGenResult.get_successful_paths` to get only successful trajectories. Returns: List[JointState]: Interpolated trajectories. Check :attr:`MotionGenResult.interpolation_dt` for the time between steps. """ path = [ self.interpolated_plan[x].trim_trajectory(0, self.path_buffer_last_tstep[x]) for x in range(len(self.interpolated_plan)) ] return path def get_successful_paths(self) -> List[torch.Tensor]: """Get successful interpolated trajectories from the result. Use for batched queries. Returns: List[JointState]: Interpolated trajectories. Check :attr:`MotionGenResult.interpolation_dt` for the time between steps. """ path = [] nz_i = torch.nonzero(self.success.view(-1)).view(-1) path = self.interpolated_plan[nz_i] path_list = [] if self.path_buffer_last_tstep is not None: for i in range(len(path)): last_tstep = self.path_buffer_last_tstep[nz_i[i]] path_list.append(path[i].trim_trajectory(0, last_tstep)) else: path_list = [path[i, :, :] for i in range(path.shape[0])] return path_list def get_interpolated_plan(self) -> JointState: """Get interpolated trajectory from the result. Returns: JointState: Interpolated trajectory. Check :attr:`MotionGenResult.interpolation_dt` for the time between steps. """ if self.path_buffer_last_tstep is None: return self.interpolated_plan if len(self.path_buffer_last_tstep) > 1: log_error("only single result is supported") return self.interpolated_plan.trim_trajectory(0, self.path_buffer_last_tstep[0]) def retime_trajectory( self, time_dilation_factor: float, interpolate_trajectory: bool = True, interpolation_dt: Optional[float] = None, interpolation_kind: InterpolateType = InterpolateType.LINEAR_CUDA, create_interpolation_buffer: bool = False, ): """Retime the optimized trajectory by a dilation factor. Args: time_dilation_factor: Time factor to slow down the trajectory. Should be less than 1.0. interpolate_trajectory: Interpolate the trajectory after retiming. interpolation_dt: Time between steps in the interpolated trajectory. If None, :attr:`MotionGenResult.interpolation_dt` is used. interpolation_kind: Interpolation type to use. create_interpolation_buffer: Create a new buffer for interpolated trajectory. Set this to True if existing buffer is not large enough to store new trajectory. """ if time_dilation_factor > 1.0: log_error("time_dilation_factor should be less than 1.0") if time_dilation_factor == 1.0: return if len(self.path_buffer_last_tstep) > 1: log_error("only single result is supported") new_dt = self.optimized_dt * (1.0 / time_dilation_factor) if len(self.optimized_plan.shape) == 3: new_dt = new_dt.view(-1, 1, 1) else: new_dt = new_dt.view(-1, 1) self.optimized_plan = self.optimized_plan.scale_by_dt(self.optimized_dt, new_dt) self.optimized_dt = new_dt.view(-1) if interpolate_trajectory: if interpolation_dt is not None: self.interpolation_dt = interpolation_dt self.interpolated_plan, last_tstep, _ = get_batch_interpolated_trajectory( self.optimized_plan, self.optimized_dt, self.interpolation_dt, kind=interpolation_kind, out_traj_state=self.interpolated_plan if not create_interpolation_buffer else None, tensor_args=self.interpolated_plan.tensor_args, optimize_dt=False, ) self.path_buffer_last_tstep = [last_tstep[i] for i in range(len(last_tstep))] if len(self.optimized_plan.shape) == 2: self.interpolated_plan = self.interpolated_plan.squeeze(0) @property def motion_time(self) -> Union[float, torch.Tensor]: """Compute motion time in seconds.""" # -2 as last three timesteps have the same value # 0, 1 also have the same position value. return self.optimized_dt * (self.optimized_plan.position.shape[-2] - 1 - 2 - 1) @staticmethod def _check_none_and_copy_idx( current_tensor: Union[torch.Tensor, JointState, None], source_tensor: Union[torch.Tensor, JointState, None], idx: int, ) -> Union[torch.Tensor, JointState]: """Helper function to copy data from source tensor to current tensor at index. Also supports copying from JointState to JointState. Args: current_tensor: tensor to copy data to. source_tensor: tensor to copy data from. idx: index to copy data at. Returns: Union[torch.Tensor, JointState]: copied tensor. """ if source_tensor is not None: if current_tensor is None: current_tensor = source_tensor.clone() else: if isinstance(current_tensor, torch.Tensor) and isinstance( source_tensor, torch.Tensor ): current_tensor[idx] = source_tensor[idx] elif isinstance(current_tensor, JointState) and isinstance( source_tensor, JointState ): source_state = source_tensor[idx] current_tensor.copy_at_index(source_state, idx) return current_tensor class MotionGen(MotionGenConfig): """Motion generation wrapper for generating collision-free trajectories. This module provides an interface to generate collision-free trajectories for manipulators. It supports Cartesian Pose for end-effectors and joint space goals. When a Cartesian Pose is used as target, it uses batched inverse kinematics to find multiple joint configuration solutions for the Cartesian Pose and then runs trajectory optimization with a linear interpolation from start configuration to each of the IK solutions. When trajectory optimization fails, it uses a graph planner to find collision-free paths to the IK solutions and then uses these paths as seeds for trajectory optimization. The module also supports batched queries for motion generation. Use this module as the high-level API for generating collision-free trajectories. """ def __init__(self, config: MotionGenConfig): """Initializes the motion generation module. Args: config: Configuration for motion generation. """ super().__init__(**vars(config)) self.rollout_fn = self.graph_planner.safety_rollout_fn self._trajopt_goal_config = None self._dof = self.rollout_fn.d_action self._batch_graph_search_buffer = None self._batch_path_buffer_last_tstep = None self._rollout_list = None self._solver_rollout_list = None self._pose_solver_rollout_list = None self._kin_list = None self.update_batch_size(seeds=self.trajopt_seeds) def update_batch_size(self, seeds=10, batch=1): """Update the batch size for motion generation. Args: seeds: Number of seeds for trajectory optimization and graph planner. batch: Number of queries to run in batch mode. """ if ( self._trajopt_goal_config is None or self._trajopt_goal_config.shape[0] != batch or self._trajopt_goal_config.shape[1] != seeds ): self._trajopt_goal_config = torch.zeros( (batch, seeds, self.rollout_fn.d_action), device=self.tensor_args.device, dtype=self.tensor_args.dtype, ) self._batch_col = ( torch.arange(0, batch, device=self.tensor_args.device, dtype=torch.long) * seeds ) if ( self._batch_graph_search_buffer is None or self._batch_graph_search_buffer.shape[0] != batch ): self._batch_graph_search_buffer = JointState.zeros( (batch, self.interpolation_steps, self.kinematics.get_dof()), tensor_args=self.tensor_args, joint_names=self.rollout_fn.joint_names, ) self._batch_path_buffer_last_tstep = [0 for i in range(batch)] def solve_ik( self, goal_pose: Pose, retract_config: Optional[T_BDOF] = None, seed_config: Optional[T_BDOF] = None, return_seeds: int = 1, num_seeds: Optional[int] = None, use_nn_seed: bool = True, newton_iters: Optional[int] = None, ) -> IKResult: """Solve inverse kinematics for a given Cartesian Pose or a batch of Poses. Use this only if your problem size is same as when using motion generation. If you want to solve IK for a different problem size, create an instance of :class:`curobo.wrap.reacher.ik_solver.IKSolver`. Args: goal_pose: Goal Pose for the end-effector. retract_config: Retract configuration for the end-effector. seed_config: Seed configuration for inverse kinematics. return_seeds: Number of solutions to return per problem query. num_seeds: Number of seeds to use for solving inverse kinematics. use_nn_seed: Use neural network seed for solving inverse kinematics. This is not implemented. newton_iters: Number of Newton iterations to run for solving inverse kinematics. This is useful to override the default number of iterations. Returns: IKResult: Result of inverse kinematics. """ return self.ik_solver.solve( goal_pose, retract_config, seed_config, return_seeds, num_seeds, use_nn_seed, newton_iters, ) @profiler.record_function("motion_gen/graph_search") def graph_search( self, start_config: T_BDOF, goal_config: T_BDOF, interpolation_steps: Optional[int] = None ) -> GraphResult: """Run graph search to find collision-free paths between start and goal configurations. Args: start_config: Start joint configurations of the robot. goal_config: Goal joint configurations of the robot. interpolation_steps: Number of interpolation steps to interpolate obtained solutions. Returns: GraphResult: Result of graph search. """ return self.graph_planner.find_paths(start_config, goal_config, interpolation_steps) def plan_single( self, start_state: JointState, goal_pose: Pose, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), link_poses: List[Pose] = None, ) -> MotionGenResult: """Plan a single motion to reach a goal pose from a start joint state. This also supports reaching target poses for different links in the robot by providing goal poses for each link in the link_poses argument. The link_poses argument should contain a pose for each link specified in :attr:`MotionGen.kinematics`. Constrained planning is supported by calling :meth:`MotionGen.update_pose_cost_metric` before calling this method. See :ref:`tut_constrained_planning` for more details. Args: start_state: Start joint state of the robot. When planning from a non-static state, i.e, when velocity or acceleration is non-zero, set :attr:`MotionGen.optimize_dt` to False. goal_pose: Goal pose for the end-effector. plan_config: Planning parameters for motion generation. link_poses: Goal poses for each link in the robot when planning for multiple links. Returns: MotionGenResult: Result of motion generation. Check :attr:`MotionGenResult.success` attribute to see if the query was successful. """ log_info("Planning for Single Goal: " + str(goal_pose.batch)) solve_state = self._get_solve_state( ReacherSolveType.SINGLE, plan_config, goal_pose, start_state ) result = self._plan_attempts( solve_state, start_state, goal_pose, plan_config, link_poses=link_poses, ) return result def plan_goalset( self, start_state: JointState, goal_pose: Pose, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), link_poses: List[Pose] = None, ) -> MotionGenResult: """Plan a single motion to reach a goal from set of poses, from a start joint state. Use this when planning to reach a grasp from a set of possible grasps. This method will try to reach the closest goal pose from the set of goal poses at every iteration of optimization during IK and trajectory optimization. This method only supports goalset for main end-effector (i.e., `goal_pose`). Use single Pose target for links in `link_poses`. Args: start_state: Start joint state of the robot. When planning from a non-static state, i.e, when velocity or acceleration is non-zero, set :attr:`MotionGen.optimize_dt` to False. goal_pose: Goal pose for the end-effector. plan_config: Planning parameters for motion generation. link_poses: Goal poses for each link in the robot when planning for multiple links. Returns: MotionGenResult: Result of motion generation. Check :attr:`MotionGenResult.success` attribute to see if the query was successful. """ solve_state = self._get_solve_state( ReacherSolveType.GOALSET, plan_config, goal_pose, start_state ) result = self._plan_attempts( solve_state, start_state, goal_pose, plan_config, link_poses=link_poses, ) return result def plan_batch( self, start_state: JointState, goal_pose: Pose, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), link_poses: Dict[str, List[Pose]] = None, ) -> MotionGenResult: """Plan motions to reach a batch of goal poses from a batch of start joint states. Args: start_state: Start joint states of the robot. When planning from a non-static state, i.e, when velocity or acceleration is non-zero, set :attr:`MotionGen.optimize_dt` to False. goal_pose: Goal poses for the end-effector. plan_config: Planning parameters for motion generation. link_poses: Goal poses for each link in the robot when planning for multiple links. Returns: MotionGenResult: Result of motion generation. Check :attr:`MotionGenResult.success` attribute to check which indices of the batch were successful. """ solve_state = self._get_solve_state( ReacherSolveType.BATCH, plan_config, goal_pose, start_state ) result = self._plan_batch_attempts( solve_state, start_state, goal_pose, plan_config, link_poses=link_poses, ) return result def plan_batch_goalset( self, start_state: JointState, goal_pose: Pose, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), link_poses: Dict[str, List[Pose]] = None, ) -> MotionGenResult: """Plan motions to reach a batch of poses (goalset) from a batch of start joint states. Args: start_state: Start joint states of the robot. When planning from a non-static state, i.e, when velocity or acceleration is non-zero, set :attr:`MotionGen.optimize_dt` to False. goal_pose: Goal poses for the end-effector. plan_config: Planning parameters for motion generation. link_poses: Goal poses for each link in the robot when planning for multiple links. Returns: MotionGenResult: Result of motion generation. Check :attr:`MotionGenResult.success` attribute to check which indices of the batch were successful. """ solve_state = self._get_solve_state( ReacherSolveType.BATCH_GOALSET, plan_config, goal_pose, start_state ) result = self._plan_batch_attempts( solve_state, start_state, goal_pose, plan_config, link_poses=link_poses, ) return result def plan_batch_env( self, start_state: JointState, goal_pose: Pose, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), link_poses: Dict[str, List[Pose]] = None, ) -> MotionGenResult: """Plan motions to reach (batch) poses in different collision environments. Args: start_state: Start joint states of the robot. When planning from a non-static state, i.e, when velocity or acceleration is non-zero, set :attr:`MotionGen.optimize_dt` to False. goal_pose: Goal poses for the end-effector. plan_config: Planning parameters for motion generation. link_poses: Goal poses for each link in the robot when planning for multiple links. Returns: MotionGenResult: Result of motion generation. Check :attr:`MotionGenResult.success` attribute to check which indices of the batch were successful. """ if plan_config.enable_graph: log_info( "Batch env mode does not support graph search, setting " + "MotionGenPlanConfig.enable_graph=False" ) plan_config.enable_graph = False if plan_config.enable_graph_attempt is not None: log_info( "Batch env mode does not support graph search, setting " + "MotionGenPlanConfig.enable_graph_attempt=None" ) plan_config.enable_graph_attempt = None solve_state = self._get_solve_state( ReacherSolveType.BATCH_ENV, plan_config, goal_pose, start_state ) result = self._plan_batch_attempts( solve_state, start_state, goal_pose, plan_config, link_poses=link_poses, ) return result def plan_batch_env_goalset( self, start_state: JointState, goal_pose: Pose, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), link_poses: Dict[str, List[Pose]] = None, ) -> MotionGenResult: """Plan motions to reach (batch) goalset poses in different collision environments. Args: start_state: Start joint states of the robot. When planning from a non-static state, i.e, when velocity or acceleration is non-zero, set :attr:`MotionGen.optimize_dt` to False. goal_pose: Goal poses for the end-effector. plan_config: Planning parameters for motion generation. link_poses: Goal poses for each link in the robot when planning for multiple links. Returns: MotionGenResult: Result of motion generation. Check :attr:`MotionGenResult.success` attribute to check which indices of the batch were successful. """ if plan_config.enable_graph: log_info( "Batch env mode does not support graph search, setting " + "MotionGenPlanConfig.enable_graph=False" ) plan_config.enable_graph = False if plan_config.enable_graph_attempt is not None: log_info( "Batch env mode does not support graph search, setting " + "MotionGenPlanConfig.enable_graph_attempt=None" ) plan_config.enable_graph_attempt = None solve_state = self._get_solve_state( ReacherSolveType.BATCH_ENV_GOALSET, plan_config, goal_pose, start_state ) result = self._plan_batch_attempts( solve_state, start_state, goal_pose, plan_config, link_poses=link_poses, ) return result def compute_kinematics(self, state: JointState) -> KinematicModelState: """Compute kinematics for a given joint state. Args: state: Joint state of the robot. Only :attr:`JointState.position` is used. Returns: KinematicModelState: Kinematic state of the robot. """ out = self.rollout_fn.compute_kinematics(state) return out @property def kinematics(self) -> CudaRobotModel: """Returns the shared kinematics model of the robot.""" return self.rollout_fn.kinematics @property def dof(self) -> int: """Returns the number of controlled degrees of freedom of the robot.""" return self.kinematics.get_dof() @property def collision_cache(self) -> Dict[str, int]: """Returns the collision cache created by the world collision checker.""" return self.world_coll_checker.cache def check_constraints(self, state: JointState) -> RolloutMetrics: """Compute IK constraints for a given joint state. Args: state: Joint state of the robot. Returns: RolloutMetrics: Metrics for the joint state. """ metrics = self.ik_solver.check_constraints(state) return metrics def update_world(self, world: WorldConfig): """Update the world representation for collision checking. This allows for updating the world representation as long as the new world representation does not have a larger number of obstacles than the :attr:`MotionGen.collision_cache` as created during initialization of :class:`MotionGenConfig`. Updating the world also invalidates the cached roadmaps in the graph planner. See :ref:`world_collision` for more details. Args: world: New world configuration for collision checking. """ self.world_coll_checker.load_collision_model(world, fix_cache_reference=self.use_cuda_graph) self.graph_planner.reset_graph() def clear_world_cache(self): """Remove all collision objects from collision cache.""" self.world_coll_checker.clear_cache() def reset(self, reset_seed=True): """Reset the motion generation module. Args: reset_seed: Reset the random seed generator. Resetting this can be time consuming, if deterministic results are not required, set this to False. """ self.graph_planner.reset_buffer() if reset_seed: self.reset_seed() def reset_seed(self): """Reset the random seed generators in all sub-modules of motion generation.""" self.rollout_fn.reset_seed() self.ik_solver.reset_seed() self.graph_planner.reset_seed() self.trajopt_solver.reset_seed() self.js_trajopt_solver.reset_seed() def get_retract_config(self) -> T_DOF: """Returns the retract/home configuration of the robot.""" return self.rollout_fn.dynamics_model.retract_config def warmup( self, enable_graph: bool = True, batch: Optional[int] = None, warmup_js_trajopt: bool = True, batch_env_mode: bool = False, parallel_finetune: bool = True, n_goalset: int = -1, warmup_joint_index: int = 0, warmup_joint_delta: float = 0.1, ): """Warmup planning methods for motion generation. Args: enable_graph: Enable graph search for warmup. batch: Number of problem queries for batch mode. If None, single query is run. warmup_js_trajopt: Warmup joint space planning in addition to Cartesian planning. batch_env_mode: Enable batch world environment mode for warmup. Only used when batch is not None. parallel_finetune: Run finetuning trajectory optimization in parallel for warmup. Leave this to True for most cases. n_goalset: Number of goal poses to use for warmup. If -1, single goal pose is used. Set this to the largest number of goals you plan to use with :meth:`MotionGen.plan_goalset`. After warmup, you can use smaller number of goals and the method will internally pad the extra goals with the first goal. warmup_joint_index: Index of the joint to perturb for warmup. warmup_joint_delta: Delta to perturb the joint for warmup. """ log_info("Warmup") if warmup_js_trajopt: start_state = JointState.from_position( self.rollout_fn.dynamics_model.retract_config.view(1, -1).clone(), joint_names=self.rollout_fn.joint_names, ) # warm up js_trajopt: goal_state = start_state.clone() goal_state.position[..., warmup_joint_index] += warmup_joint_delta for _ in range(3): self.plan_single_js( start_state.clone(), goal_state.clone(), MotionGenPlanConfig(max_attempts=1, enable_finetune_trajopt=True), ) if enable_graph: start_state = JointState.from_position( self.rollout_fn.dynamics_model.retract_config.view(1, -1).clone(), joint_names=self.rollout_fn.joint_names, ) start_state.position[..., warmup_joint_index] += warmup_joint_delta self.graph_planner.warmup( self.rollout_fn.dynamics_model.retract_config.view(1, -1).clone(), start_state.position, ) if batch is None: start_state = JointState.from_position( self.rollout_fn.dynamics_model.retract_config.view(1, -1).clone(), joint_names=self.rollout_fn.joint_names, ) state = self.rollout_fn.compute_kinematics(start_state) link_poses = state.link_pose if n_goalset == -1: retract_pose = Pose(state.ee_pos_seq, quaternion=state.ee_quat_seq) start_state.position[..., warmup_joint_index] += warmup_joint_delta for _ in range(3): self.plan_single( start_state, retract_pose, MotionGenPlanConfig( max_attempts=1, enable_finetune_trajopt=True, parallel_finetune=parallel_finetune, ), link_poses=link_poses, ) self.plan_single( start_state, retract_pose, MotionGenPlanConfig( max_attempts=1, enable_finetune_trajopt=True, enable_graph=enable_graph, parallel_finetune=parallel_finetune, ), link_poses=link_poses, ) else: retract_pose = Pose( state.ee_pos_seq.repeat(n_goalset, 1).view(1, n_goalset, 3), quaternion=state.ee_quat_seq.repeat(n_goalset, 1).view(1, n_goalset, 4), ) start_state.position[..., warmup_joint_index] += warmup_joint_delta for _ in range(3): self.plan_goalset( start_state, retract_pose, MotionGenPlanConfig( max_attempts=1, enable_finetune_trajopt=True, parallel_finetune=parallel_finetune, ), link_poses=link_poses, ) self.plan_goalset( start_state, retract_pose, MotionGenPlanConfig( max_attempts=1, enable_finetune_trajopt=True, enable_graph=enable_graph, parallel_finetune=parallel_finetune, ), link_poses=link_poses, ) else: start_state = JointState.from_position( self.get_retract_config().view(1, -1).clone(), joint_names=self.rollout_fn.joint_names, ).repeat_seeds(batch) state = self.rollout_fn.compute_kinematics(start_state) link_poses = state.link_pose if n_goalset == -1: retract_pose = Pose(state.ee_pos_seq, quaternion=state.ee_quat_seq) start_state.position[..., warmup_joint_index] += warmup_joint_delta for _ in range(3): if batch_env_mode: self.plan_batch_env( start_state, retract_pose, MotionGenPlanConfig( max_attempts=10, enable_finetune_trajopt=True, enable_graph=False, enable_graph_attempt=None, ), link_poses=link_poses, ) else: self.plan_batch( start_state, retract_pose, MotionGenPlanConfig( max_attempts=10, enable_finetune_trajopt=True, enable_graph=enable_graph, enable_graph_attempt=None if not enable_graph else 20, ), link_poses=link_poses, ) else: retract_pose = Pose( state.ee_pos_seq.view(batch, 1, 3).repeat(1, n_goalset, 1).contiguous(), quaternion=state.ee_quat_seq.view(batch, 1, 4) .repeat(1, n_goalset, 1) .contiguous(), ) start_state.position[..., warmup_joint_index] += warmup_joint_delta for _ in range(3): if batch_env_mode: self.plan_batch_env_goalset( start_state, retract_pose, MotionGenPlanConfig( max_attempts=10, enable_finetune_trajopt=True, enable_graph=False, ), link_poses=link_poses, ) else: self.plan_batch_goalset( start_state, retract_pose, MotionGenPlanConfig( max_attempts=10, enable_finetune_trajopt=True, enable_graph=enable_graph, enable_graph_attempt=None if not enable_graph else 20, ), link_poses=link_poses, ) log_info("Warmup complete") def plan_single_js( self, start_state: JointState, goal_state: JointState, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), ) -> MotionGenResult: """Plan a single motion to reach a goal joint state from a start joint state. This method uses trajectory optimization to find a collision-free path between the start and goal joint states. If trajectory optimization fails, it uses a graph planner to find a collision-free path to the goal joint state. The graph plan is then used as a seed for trajectory optimization. Args: start_state: Start joint state of the robot. goal_state: Goal joint state of the robot. plan_config: Planning parameters for motion generation. Returns: MotionGenResult: Result of motion generation. Check :attr:`MotionGenResult.success` attribute to see if the query was successful. """ start_time = time.time() time_dict = { "solve_time": 0, "ik_time": 0, "graph_time": 0, "trajopt_time": 0, "trajopt_attempts": 0, "finetune_time": 0, } result = None # goal = Goal(goal_state=goal_state, current_state=start_state) solve_state = ReacherSolveState( ReacherSolveType.SINGLE, num_ik_seeds=1, num_trajopt_seeds=self.js_trajopt_solver.num_seeds, num_graph_seeds=self.js_trajopt_solver.num_seeds, batch_size=1, n_envs=1, n_goalset=1, ) force_graph = plan_config.enable_graph valid_query = True if plan_config.check_start_validity: valid_query, status = self.check_start_state(start_state) if not valid_query: result = MotionGenResult( success=torch.as_tensor([False], device=self.tensor_args.device), valid_query=valid_query, status=status, ) return result for n in range(plan_config.max_attempts): result = self._plan_js_from_solve_state( solve_state, start_state, goal_state, plan_config=plan_config ) time_dict["trajopt_time"] += result.trajopt_time time_dict["graph_time"] += result.graph_time time_dict["finetune_time"] += result.finetune_time time_dict["trajopt_attempts"] = n if plan_config.enable_graph_attempt is not None and ( n >= plan_config.enable_graph_attempt - 1 and not plan_config.enable_graph ): plan_config.enable_graph = True if plan_config.disable_graph_attempt is not None and ( n >= plan_config.disable_graph_attempt - 1 and not force_graph ): plan_config.enable_graph = False if result.success.item(): break if not result.valid_query: break if time.time() - start_time > plan_config.timeout: break result.graph_time = time_dict["graph_time"] result.finetune_time = time_dict["finetune_time"] result.trajopt_time = time_dict["trajopt_time"] result.solve_time = result.trajopt_time + result.graph_time + result.finetune_time result.total_time = result.solve_time result.attempts = n if plan_config.time_dilation_factor is not None and torch.count_nonzero(result.success) > 0: result.retime_trajectory( plan_config.time_dilation_factor, interpolation_kind=self.js_trajopt_solver.interpolation_type, ) return result def solve_trajopt( self, goal: Goal, act_seed, return_all_solutions: bool = False, num_seeds: Optional[int] = None, ): """Solve trajectory optimization for a given goal. Args: goal: Goal for trajectory optimization. act_seed: Seed for trajectory optimization. return_all_solutions: Return results for all seeds in trajectory optimization. num_seeds: Override number of seeds to use for trajectory optimization. Returns: TrajOptResult: Result of trajectory optimization. """ result = self.trajopt_solver.solve( goal, act_seed, return_all_solutions=return_all_solutions, num_seeds=num_seeds ) return result def get_active_js( self, in_js: JointState, ): """Get controlled joint state from input joint state. This is used to get the joint state for only joints that are optimization variables. This also re-orders the joints to match the order of optimization variables. Args: in_js: Input joint state. Returns: JointState: Active joint state. """ opt_jnames = self.rollout_fn.joint_names opt_js = in_js.get_ordered_joint_state(opt_jnames) return opt_js def update_pose_cost_metric( self, metric: PoseCostMetric, start_state: Optional[JointState] = None, goal_pose: Optional[Pose] = None, ) -> bool: """Update the pose cost metric for :ref:`tut_constrained_planning`. Only supports for the main end-effector. Does not support for multiple links that are specified with `link_poses` in planning methods. Args: metric: Type and parameters for pose constraint to add. start_state: Start joint state for the constraint. goal_pose: Goal pose for the constraint. Returns: bool: True if the constraint can be added, False otherwise. """ # check if constraint is valid: if metric.hold_partial_pose and metric.offset_tstep_fraction < 0.0: start_pose = self.compute_kinematics(start_state).ee_pose.clone() if self.project_pose_to_goal_frame: # project start pose to goal frame: projected_pose = goal_pose.compute_local_pose(start_pose) if torch.count_nonzero(metric.hold_vec_weight[:3] > 0.0) > 0: # angular distance should be zero: distance = projected_pose.angular_distance( Pose.from_list([0, 0, 0, 1, 0, 0, 0], tensor_args=self.tensor_args) ) if torch.max(distance) > 0.05: log_warn( "Partial orientation between start and goal is not equal" + str(distance) ) return False # check linear distance: if ( torch.count_nonzero( torch.abs(projected_pose.position[..., metric.hold_vec_weight[3:] > 0.0]) > 0.005 ) > 0 ): log_warn("Partial position between start and goal is not equal.") return False else: # project start pose to goal frame: projected_position = goal_pose.position - start_pose.position # check linear distance: if ( torch.count_nonzero( torch.abs(projected_position[..., metric.hold_vec_weight[3:] > 0.0]) > 0.005 ) > 0 ): log_warn("Partial position between start and goal is not equal.") return False rollouts = self.get_all_pose_solver_rollout_instances() [ rollout.update_pose_cost_metric(metric) for rollout in rollouts if isinstance(rollout, ArmReacher) ] torch.cuda.synchronize(device=self.tensor_args.device) return True def get_all_rollout_instances(self) -> List[RolloutBase]: """Get all rollout instances used across components in motion generation.""" if self._rollout_list is None: self._rollout_list = ( self.ik_solver.get_all_rollout_instances() + self.graph_planner.get_all_rollout_instances() + self.trajopt_solver.get_all_rollout_instances() + self.finetune_trajopt_solver.get_all_rollout_instances() + self.js_trajopt_solver.get_all_rollout_instances() + self.finetune_js_trajopt_solver.get_all_rollout_instances() ) return self._rollout_list def get_all_solver_rollout_instances(self) -> List[RolloutBase]: """Get all rollout instances in solvers (IK, TrajOpt).""" if self._solver_rollout_list is None: self._solver_rollout_list = ( self.ik_solver.solver.get_all_rollout_instances() + self.trajopt_solver.solver.get_all_rollout_instances() + self.finetune_trajopt_solver.solver.get_all_rollout_instances() + self.js_trajopt_solver.solver.get_all_rollout_instances() + self.finetune_js_trajopt_solver.solver.get_all_rollout_instances() ) return self._solver_rollout_list def get_all_pose_solver_rollout_instances(self) -> List[RolloutBase]: """Get all rollout instances in solvers (IK, TrajOpt) that support Cartesian cost terms.""" if self._pose_solver_rollout_list is None: self._pose_solver_rollout_list = ( self.ik_solver.solver.get_all_rollout_instances() + self.trajopt_solver.solver.get_all_rollout_instances() + self.finetune_trajopt_solver.solver.get_all_rollout_instances() ) return self._pose_solver_rollout_list def get_all_kinematics_instances(self) -> List[CudaRobotModel]: """Get all kinematics instances used across components in motion generation. This is deprecated. Use :meth:`MotionGen.kinematics` instead as MotionGen now uses a shared kinematics instance across all components. Returns: List[CudaRobotModel]: Single kinematics instance, returned as a list for compatibility. """ if self._kin_list is None: self._kin_list = [ i.dynamics_model.robot_model for i in self.get_all_rollout_instances() ] return self._kin_list def attach_objects_to_robot( self, joint_state: JointState, object_names: List[str], surface_sphere_radius: float = 0.001, link_name: str = "attached_object", sphere_fit_type: SphereFitType = SphereFitType.VOXEL_VOLUME_SAMPLE_SURFACE, voxelize_method: str = "ray", world_objects_pose_offset: Optional[Pose] = None, remove_obstacles_from_world_config: bool = False, ) -> None: """Attach an object or objects from world to a robot's link. This method assumes that the objects exist in the world configuration. If attaching objects that are not in world, use :meth:`MotionGen.attach_external_objects_to_robot`. Args: joint_state: Joint state of the robot. object_names: Names of objects in the world to attach to the robot. surface_sphere_radius: Radius (in meters) to use for points sampled on surface of the object. A smaller radius will allow for generating motions very close to obstacles. link_name: Name of the link (frame) to attach the objects to. The assumption is that this link does not have any geometry and all spheres of this link represent attached objects. sphere_fit_type: Sphere fit algorithm to use. See :ref:`attach_object_note` for more details. The default method :attr:`SphereFitType.VOXEL_VOLUME_SAMPLE_SURFACE` voxelizes the volume of the objects and adds spheres representing the voxels, then samples points on the surface of the object, adds :attr:`surface_sphere_radius` to these points. This should be used for most cases. voxelize_method: Method to use for voxelization, passed to :py:func:`trimesh.voxel.creation.voxelize`. world_objects_pose_offset: Offset to apply to the object poses before attaching to the robot. This is useful when attaching an object that's in contact with the world. The offset is applied in the world frame before attaching to the robot. remove_obstacles_from_world_config: Remove the obstacles from the world cache after attaching to the robot to reduce memory usage. Note that when an object is attached to the robot, it's disabled in the world collision checker. This flag when enabled, also removes the object from world cache. For most cases, this should be set to False. """ log_info("MG: Attach objects to robot") kin_state = self.compute_kinematics(joint_state) ee_pose = kin_state.ee_pose # w_T_ee if world_objects_pose_offset is not None: # add offset from ee: ee_pose = world_objects_pose_offset.inverse().multiply(ee_pose) # new ee_pose: # w_T_ee = offset_T_w * w_T_ee # ee_T_w ee_pose = ee_pose.inverse() # ee_T_w to multiply all objects later max_spheres = self.robot_cfg.kinematics.kinematics_config.get_number_of_spheres(link_name) n_spheres = int(max_spheres / len(object_names)) sphere_tensor = torch.zeros((max_spheres, 4)) sphere_tensor[:, 3] = -10.0 sph_list = [] if n_spheres == 0: log_error("MG: No spheres found") for i, x in enumerate(object_names): obs = self.world_model.get_obstacle(x) if obs is None: log_error( "Object not found in world. Object name: " + x + " Name of objects in world: " + " ".join([i.name for i in self.world_model.objects]) ) sph = obs.get_bounding_spheres( n_spheres, surface_sphere_radius, pre_transform_pose=ee_pose, tensor_args=self.tensor_args, fit_type=sphere_fit_type, voxelize_method=voxelize_method, ) sph_list += [s.position + [s.radius] for s in sph] self.world_coll_checker.enable_obstacle(enable=False, name=x) if remove_obstacles_from_world_config: self.world_model.remove_obstacle(x) log_info("MG: Computed spheres for attach objects to robot") spheres = self.tensor_args.to_device(torch.as_tensor(sph_list)) if spheres.shape[0] > max_spheres: spheres = spheres[: spheres.shape[0]] sphere_tensor[: spheres.shape[0], :] = spheres.contiguous() self.attach_spheres_to_robot(sphere_tensor=sphere_tensor, link_name=link_name) def attach_external_objects_to_robot( self, joint_state: JointState, external_objects: List[Obstacle], surface_sphere_radius: float = 0.001, link_name: str = "attached_object", sphere_fit_type: SphereFitType = SphereFitType.VOXEL_VOLUME_SAMPLE_SURFACE, voxelize_method: str = "ray", world_objects_pose_offset: Optional[Pose] = None, ) -> None: """Attach external objects (not in world model) to a robot's link. Args: joint_state: Joint state of the robot. external_objects: List of external objects to attach to the robot. surface_sphere_radius: Radius (in meters) to use for points sampled on surface of the object. A smaller radius will allow for generating motions very close to obstacles. link_name: Name of the link (frame) to attach the objects to. The assumption is that this link does not have any geometry and all spheres of this link represent attached objects. sphere_fit_type: Sphere fit algorithm to use. See :ref:`attach_object_note` for more details. The default method :attr:`SphereFitType.VOXEL_VOLUME_SAMPLE_SURFACE` voxelizes the volume of the objects and adds spheres representing the voxels, then samples points on the surface of the object, adds :attr:`surface_sphere_radius` to these points. This should be used for most cases. voxelize_method: Method to use for voxelization, passed to :py:func:`trimesh.voxel.creation.voxelize`. world_objects_pose_offset: Offset to apply to the object poses before attaching to the robot. This is useful when attaching an object that's in contact with the world. The offset is applied in the world frame before attaching to the robot. """ log_info("MG: Attach objects to robot") if len(external_objects) == 0: log_error("no object in external_objects") kin_state = self.compute_kinematics(joint_state) ee_pose = kin_state.ee_pose # w_T_ee if world_objects_pose_offset is not None: # add offset from ee: ee_pose = world_objects_pose_offset.inverse().multiply(ee_pose) # new ee_pose: # w_T_ee = offset_T_w * w_T_ee # ee_T_w ee_pose = ee_pose.inverse() # ee_T_w to multiply all objects later max_spheres = self.robot_cfg.kinematics.kinematics_config.get_number_of_spheres(link_name) object_names = [x.name for x in external_objects] n_spheres = int(max_spheres / len(object_names)) sphere_tensor = torch.zeros((max_spheres, 4)) sphere_tensor[:, 3] = -10.0 sph_list = [] if n_spheres == 0: log_error("MG: No spheres found") for i, x in enumerate(object_names): obs = external_objects[i] sph = obs.get_bounding_spheres( n_spheres, surface_sphere_radius, pre_transform_pose=ee_pose, tensor_args=self.tensor_args, fit_type=sphere_fit_type, voxelize_method=voxelize_method, ) sph_list += [s.position + [s.radius] for s in sph] log_info("MG: Computed spheres for attach objects to robot") spheres = self.tensor_args.to_device(torch.as_tensor(sph_list)) if spheres.shape[0] > max_spheres: spheres = spheres[: spheres.shape[0]] sphere_tensor[: spheres.shape[0], :] = spheres.contiguous() self.attach_spheres_to_robot(sphere_tensor=sphere_tensor, link_name=link_name) def add_camera_frame(self, camera_observation: CameraObservation, obstacle_name: str): """Add camera frame to the world collision checker. Only supported by :py:class:`~curobo.geom.sdf.world_blox.WorldBloxCollision`. Args: camera_observation: Camera observation to add to the world collision checker. obstacle_name: Name of the obstacle/layer to add the camera frame to. """ self.world_coll_checker.add_camera_frame(camera_observation, obstacle_name) def process_camera_frames(self, obstacle_name: Optional[str] = None, process_aux: bool = False): """Process camera frames for collision checking. Only supported by :py:class:`~curobo.geom.sdf.world_blox.WorldBloxCollision`. Args: obstacle_name: Name of the obstacle/layer to process the camera frames for. If None, processes camera frames on all supported obstacles. process_aux: Process auxillary information such as mesh integration in nvblox. This is not required for collision checking and is only needed for debugging. Default is False to reduce computation time. """ self.world_coll_checker.process_camera_frames(obstacle_name, process_aux=process_aux) def attach_bounding_box_from_blox_to_robot( self, joint_state: JointState, bounding_box: Cuboid, blox_layer_name: Optional[str] = None, surface_sphere_radius: float = 0.001, link_name: str = "attached_object", sphere_fit_type: SphereFitType = SphereFitType.VOXEL_VOLUME_SAMPLE_SURFACE, voxelize_method: str = "ray", world_objects_pose_offset: Optional[Pose] = None, ): """Attach the voxels in a blox layer to robot's link. .. note:: This is not currently implemented. """ log_error("Not implemented") kin_state = self.compute_kinematics(joint_state) ee_pose = kin_state.ee_pose # w_T_ee if world_objects_pose_offset is not None: # add offset from ee: ee_pose = world_objects_pose_offset.inverse().multiply(ee_pose) # new ee_pose: # w_T_ee = offset_T_w * w_T_ee # ee_T_w ee_pose = ee_pose.inverse() # ee_T_w to multiply all objects later max_spheres = self.robot_cfg.kinematics.kinematics_config.get_number_of_spheres(link_name) n_spheres = max_spheres sphere_tensor = torch.zeros((max_spheres, 4)) sphere_tensor[:, 3] = -10.0 sph_list = [] if n_spheres == 0: log_error("MG: No spheres found") sph = self.world_coll_checker.get_bounding_spheres( bounding_box, blox_layer_name, n_spheres, surface_sphere_radius, sphere_fit_type, voxelize_method, pre_transform_pose=ee_pose, clear_region=True, ) sph_list += [s.position + [s.radius] for s in sph] log_info("MG: Computed spheres for attach objects to robot") spheres = self.tensor_args.to_device(torch.as_tensor(sph_list)) if spheres.shape[0] > max_spheres: spheres = spheres[: spheres.shape[0]] sphere_tensor[: spheres.shape[0], :] = spheres.contiguous() self.attach_spheres_to_robot(sphere_tensor=sphere_tensor, link_name=link_name) def attach_new_object_to_robot( self, joint_state: JointState, obstacle: Obstacle, surface_sphere_radius: float = 0.001, link_name: str = "attached_object", sphere_fit_type: SphereFitType = SphereFitType.VOXEL_VOLUME_SAMPLE_SURFACE, voxelize_method: str = "ray", world_objects_pose_offset: Optional[Pose] = None, ): """Attach an object to robot's link. The object to be attached is not in the world model. Deprecated. Use :meth:`MotionGen.attach_external_objects_to_robot` instead. """ log_warn("Deprecated. Use attach_external_objects_to_robot instead") return self.attach_external_objects_to_robot( joint_state=joint_state, external_objects=[obstacle], surface_sphere_radius=surface_sphere_radius, link_name=link_name, sphere_fit_type=sphere_fit_type, voxelize_method=voxelize_method, world_objects_pose_offset=world_objects_pose_offset, ) def detach_object_from_robot(self, link_name: str = "attached_object") -> None: """Detach object from robot's link. Args: link_name: Name of the link. """ self.detach_spheres_from_robot(link_name) def attach_spheres_to_robot( self, sphere_radius: Optional[float] = None, sphere_tensor: Optional[torch.Tensor] = None, link_name: str = "attached_object", ) -> None: """Attach spheres to robot's link. Args: sphere_radius: Radius of the spheres. Set to None if :attr:`sphere_tensor` is provided. sphere_tensor: Sphere x, y, z, r tensor. link_name: Name of the link to attach the spheres to. Note that this link should already have pre-allocated spheres. """ self.robot_cfg.kinematics.kinematics_config.attach_object( sphere_radius=sphere_radius, sphere_tensor=sphere_tensor, link_name=link_name ) def detach_spheres_from_robot(self, link_name: str = "attached_object") -> None: """Detach spheres from a robot's link. Args: link_name: Name of the link. """ self.robot_cfg.kinematics.kinematics_config.detach_object(link_name) def get_full_js(self, active_js: JointState) -> JointState: """Get full joint state from controlled joint state, appending locked joints. Args: active_js: Controlled joint state Returns: JointState: Full joint state. """ return self.rollout_fn.get_full_dof_from_solution(active_js) @property def world_model(self) -> WorldConfig: """Get the world model used for collision checking.""" return self.world_coll_checker.world_model @property def world_collision(self) -> WorldCollision: """Get the shared instance of world collision checker.""" return self.world_coll_checker @property def project_pose_to_goal_frame(self) -> bool: """Check if the pose cost metric is projected to goal frame.""" return self.trajopt_solver.rollout_fn.goal_cost.project_distance @property def joint_names(self) -> List[str]: """Get the joint names of the robot.""" return self.rollout_fn.joint_names def update_interpolation_type( self, interpolation_type: InterpolateType, update_graph: bool = True, update_trajopt: bool = True, ): """Update interpolation type for motion generation. Args: interpolation_type: Type of interpolation to use. update_graph: Update graph planner with the new interpolation type. update_trajopt: Update trajectory optimization solvers with the new interpolation type. """ if update_graph: self.graph_planner.interpolation_type = interpolation_type if update_trajopt: self.trajopt_solver.interpolation_type = interpolation_type self.finetune_trajopt_solver.interpolation_type = interpolation_type self.js_trajopt_solver.interpolation_type = interpolation_type self.finetune_js_trajopt_solver.interpolation_type = interpolation_type def update_locked_joints( self, lock_joints: Dict[str, float], robot_config_dict: Union[str, Dict[Any]] ): """Update locked joints in the robot configuration. Use this function to update the joint values of currently locked joints between planning calls. This function can also be used to change which joints are locked, however this is only supported when the number of locked joints is the same as the original robot configuration as the kinematics tensors are pre-allocated. Args: lock_joints: Dictionary of joint names and values to lock. robot_config_dict: Robot configuration dictionary or path to robot configuration file. """ if isinstance(robot_config_dict, str): robot_config_dict = load_yaml(join_path(get_robot_configs_path(), robot_config_dict))[ "robot_cfg" ] if "robot_cfg" in robot_config_dict: robot_config_dict = robot_config_dict["robot_cfg"] robot_config_dict["kinematics"]["lock_joints"] = lock_joints robot_cfg = RobotConfig.from_dict(robot_config_dict, self.tensor_args) self.kinematics.update_kinematics_config(robot_cfg.kinematics.kinematics_config) def check_start_state( self, start_state: JointState ) -> Tuple[bool, Union[None, MotionGenStatus]]: """Check if the start state is valid for motion generation. Args: start_state: Start joint state of the robot. Returns: Tuple[bool, MotionGenStatus]: Tuple containing True if the start state is valid and the status of the start state. """ joint_position = start_state.position if len(joint_position.shape) == 1: joint_position = joint_position.unsqueeze(0) if len(joint_position.shape) > 2: log_error("joint_position should be of shape (batch, dof)") joint_position = joint_position.unsqueeze(1) metrics = self.rollout_fn.rollout_constraint( joint_position, use_batch_env=False, ) valid_query = metrics.feasible.squeeze(1).item() status = None if not valid_query: self.rollout_fn.primitive_collision_constraint.disable_cost() self.rollout_fn.robot_self_collision_constraint.disable_cost() within_joint_limits = ( self.rollout_fn.rollout_constraint( joint_position, use_batch_env=False, ) .feasible.squeeze(1) .item() ) self.rollout_fn.primitive_collision_constraint.enable_cost() if not within_joint_limits: self.rollout_fn.robot_self_collision_constraint.enable_cost() return valid_query, MotionGenStatus.INVALID_START_STATE_JOINT_LIMITS self.rollout_fn.primitive_collision_constraint.enable_cost() world_collision_free = ( self.rollout_fn.rollout_constraint( joint_position, use_batch_env=False, ) .feasible.squeeze(1) .item() ) if not world_collision_free: return valid_query, MotionGenStatus.INVALID_START_STATE_WORLD_COLLISION self.rollout_fn.robot_self_collision_constraint.enable_cost() self_collision_free = ( self.rollout_fn.rollout_constraint( joint_position, use_batch_env=False, ) .feasible.squeeze(1) .item() ) if not self_collision_free: return valid_query, MotionGenStatus.INVALID_START_STATE_SELF_COLLISION status = MotionGenStatus.INVALID_START_STATE_UNKNOWN_ISSUE return (valid_query, status) @profiler.record_function("motion_gen/ik") def _solve_ik_from_solve_state( self, goal_pose: Pose, solve_state: ReacherSolveState, start_state: JointState, use_nn_seed: bool, partial_ik_opt: bool, link_poses: Optional[Dict[str, Pose]] = None, ) -> IKResult: """Solve inverse kinematics from solve state, used by motion generation planning call. Args: goal_pose: Goal Pose for the end-effector. solve_state: Solve state for motion generation. start_state: Start joint configuration of the robot. use_nn_seed: Use seed from a neural network. Not implemented. partial_ik_opt: Only run 50 iterations of inverse kinematics. link_poses: Goal Poses of any other link in the robot that was specified in :meth:`curobo.types.robot.RobotConfig.kinematics.link_names`. Returns: IKResult: Result of inverse kinematics. """ newton_iters = None if partial_ik_opt: newton_iters = self.partial_ik_iters ik_result = self.ik_solver.solve_any( solve_state.solve_type, goal_pose, start_state.position.view(-1, self._dof), start_state.position.view(-1, 1, self._dof), solve_state.num_trajopt_seeds, solve_state.num_ik_seeds, use_nn_seed, newton_iters, link_poses, ) return ik_result @profiler.record_function("motion_gen/trajopt_solve") def _solve_trajopt_from_solve_state( self, goal: Goal, solve_state: ReacherSolveState, act_seed: Optional[JointState] = None, use_nn_seed: bool = False, return_all_solutions: bool = False, seed_success: Optional[torch.Tensor] = None, newton_iters: Optional[int] = None, trajopt_instance: Optional[TrajOptSolver] = None, num_seeds_override: Optional[int] = None, ) -> TrajOptResult: """Solve trajectory optimization from solve state, used by motion generation planning call. Args: goal: Goal containing Pose/Joint targets, current robot state and any other information. solve_state: Solve state for motion generation. act_seed: Seed to run trajectory optimization. use_nn_seed: Use neural network seed for solving trajectory optimization. This is not implemented. return_all_solutions: Return all solutions found by trajectory optimization. seed_success: Success tensor for seeds. newton_iters: Override Newton iterations to run for solving trajectory optimization. trajopt_instance: Instance of TrajOptSolver to use for solving trajectory optimization. num_seeds_override: Override number of seeds to use for solving trajectory optimization. Returns: TrajOptResult: Result of trajectory optimization. """ if trajopt_instance is None: trajopt_instance = self.trajopt_solver if num_seeds_override is None: num_seeds_override = solve_state.num_trajopt_seeds traj_result = trajopt_instance.solve_any( solve_state.solve_type, goal, act_seed, use_nn_seed, return_all_solutions, num_seeds_override, seed_success, newton_iters=newton_iters, ) return traj_result def _get_solve_state( self, solve_type: ReacherSolveType, plan_config: MotionGenPlanConfig, goal_pose: Pose, start_state: JointState, ) -> ReacherSolveState: """Generate solve state for motion generation based on planning type and configuration. MotionGen creates a :class:`ReacherSolveState` for every planning call to keep track of planning parameters such as number of seeds, batch size, solve type, etc. This solve state is then compared with existing solve state to determine if solvers (IK, TrajOpt) need to be re-initialized. Note that changing solve state is not supported when :attr:`MotionGen.use_cuda_graph` is enabled. Args: solve_type: Type of reacher problem to solve. plan_config: Planning configuration for motion generation. goal_pose: Goal Pose for the end-effector. start_state: Start joint configuration of the robot. Raises: ValueError: If the solve type is not supported. Returns: ReacherSolveState: Solve state for motion generation. """ num_ik_seeds = ( self.ik_seeds if plan_config.num_ik_seeds is None else plan_config.num_ik_seeds ) num_trajopt_seeds = ( self.trajopt_seeds if plan_config.num_trajopt_seeds is None else plan_config.num_trajopt_seeds ) num_graph_seeds = ( self.trajopt_seeds if plan_config.num_graph_seeds is None else num_trajopt_seeds ) solve_state = None if solve_type == ReacherSolveType.SINGLE: solve_state = ReacherSolveState( solve_type, num_ik_seeds=num_ik_seeds, num_trajopt_seeds=num_trajopt_seeds, num_graph_seeds=num_graph_seeds, batch_size=1, n_envs=1, n_goalset=1, ) elif solve_type == ReacherSolveType.GOALSET: solve_state = ReacherSolveState( solve_type, num_ik_seeds=num_ik_seeds, num_trajopt_seeds=num_trajopt_seeds, num_graph_seeds=num_graph_seeds, batch_size=1, n_envs=1, n_goalset=goal_pose.n_goalset, ) elif solve_type == ReacherSolveType.BATCH: solve_state = ReacherSolveState( solve_type, num_ik_seeds=num_ik_seeds, num_trajopt_seeds=num_trajopt_seeds, num_graph_seeds=num_graph_seeds, batch_size=goal_pose.batch, n_envs=1, n_goalset=1, ) elif solve_type == ReacherSolveType.BATCH_GOALSET: solve_state = ReacherSolveState( solve_type, num_ik_seeds=num_ik_seeds, num_trajopt_seeds=num_trajopt_seeds, num_graph_seeds=num_graph_seeds, batch_size=goal_pose.batch, n_envs=1, n_goalset=goal_pose.n_goalset, ) elif solve_type == ReacherSolveType.BATCH_ENV: solve_state = ReacherSolveState( solve_type, num_ik_seeds=num_ik_seeds, num_trajopt_seeds=num_trajopt_seeds, num_graph_seeds=num_graph_seeds, batch_size=goal_pose.batch, n_envs=goal_pose.batch, n_goalset=1, ) elif solve_type == ReacherSolveType.BATCH_ENV_GOALSET: solve_state = ReacherSolveState( solve_type, num_ik_seeds=num_ik_seeds, num_trajopt_seeds=num_trajopt_seeds, num_graph_seeds=num_graph_seeds, batch_size=goal_pose.batch, n_envs=goal_pose.batch, n_goalset=goal_pose.n_goalset, ) else: raise ValueError("Unsupported Solve type") return solve_state def _plan_attempts( self, solve_state: ReacherSolveState, start_state: JointState, goal_pose: Pose, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), link_poses: List[Pose] = None, ): """Call many planning attempts for a given reacher solve state. Args: solve_state: Reacher solve state for planning. start_state: Start joint state for planning. goal_pose: Goal pose to reach for end-effector. plan_config: Planning parameters for motion generation. link_poses: Goal poses for other links in the robot. Returns: MotionGenResult: Result of planning. """ start_time = time.time() valid_query = True if plan_config.check_start_validity: valid_query, status = self.check_start_state(start_state) if not valid_query: result = MotionGenResult( success=torch.as_tensor([False], device=self.tensor_args.device), valid_query=valid_query, status=status, ) return result if plan_config.pose_cost_metric is not None: valid_query = self.update_pose_cost_metric( plan_config.pose_cost_metric, start_state, goal_pose ) if not valid_query: result = MotionGenResult( success=torch.as_tensor([False], device=self.tensor_args.device), valid_query=valid_query, status="Invalid Hold Partial Pose", ) return result self.update_batch_size(seeds=solve_state.num_trajopt_seeds, batch=solve_state.batch_size) if solve_state.batch_env: if solve_state.batch_size > self.world_coll_checker.n_envs: log_error("Batch Env is less that goal batch") force_graph = plan_config.enable_graph partial_ik = plan_config.partial_ik_opt ik_fail_count = 0 time_dict = { "solve_time": 0, "ik_time": 0, "graph_time": 0, "trajopt_time": 0, "trajopt_attempts": 0, } best_status = 0 if plan_config.finetune_dt_scale is None: plan_config.finetune_dt_scale = self.finetune_dt_scale for n in range(plan_config.max_attempts): log_info("MG Iter: " + str(n)) result = self._plan_from_solve_state( solve_state, start_state, goal_pose, plan_config, link_poses, ) time_dict["solve_time"] += result.solve_time time_dict["ik_time"] += result.ik_time time_dict["graph_time"] += result.graph_time time_dict["trajopt_time"] += result.trajopt_time time_dict["trajopt_attempts"] += result.trajopt_attempts if ( result.status == MotionGenStatus.IK_FAIL and plan_config.ik_fail_return is not None ): # IF IK fails the first time, we exist assuming the goal is not reachable ik_fail_count += 1 best_status = max(best_status, 1) if ik_fail_count > plan_config.ik_fail_return: break if result.success[0].item(): break if result.status == MotionGenStatus.FINETUNE_TRAJOPT_FAIL: plan_config.finetune_dt_scale *= ( plan_config.finetune_dt_decay**plan_config.finetune_attempts ) plan_config.finetune_dt_scale = min(plan_config.finetune_dt_scale, 1.25) if plan_config.enable_graph_attempt is not None and ( n >= plan_config.enable_graph_attempt - 1 and result.status in [MotionGenStatus.TRAJOPT_FAIL, MotionGenStatus.FINETUNE_TRAJOPT_FAIL] and not plan_config.enable_graph ): plan_config.enable_graph = True plan_config.partial_ik_opt = False if plan_config.disable_graph_attempt is not None and ( n >= plan_config.disable_graph_attempt - 1 and result.status in [ MotionGenStatus.TRAJOPT_FAIL, MotionGenStatus.GRAPH_FAIL, MotionGenStatus.FINETUNE_TRAJOPT_FAIL, ] and not force_graph ): plan_config.enable_graph = False plan_config.partial_ik_opt = partial_ik if result.status in [MotionGenStatus.TRAJOPT_FAIL]: best_status = 3 elif result.status in [MotionGenStatus.GRAPH_FAIL]: best_status = 2 if time.time() - start_time > plan_config.timeout: break if not result.valid_query: result.status = MotionGenStatus.INVALID_QUERY break if n == 10: self.reset_seed() log_warn("Couldn't find solution with 10 attempts, resetting seeds") result.solve_time = time_dict["solve_time"] result.ik_time = time_dict["ik_time"] result.graph_time = time_dict["graph_time"] result.trajopt_time = time_dict["trajopt_time"] result.trajopt_attempts = time_dict["trajopt_attempts"] result.attempts = n + 1 torch.cuda.synchronize(device=self.tensor_args.device) if plan_config.pose_cost_metric is not None: self.update_pose_cost_metric(PoseCostMetric.reset_metric()) if plan_config.time_dilation_factor is not None and torch.count_nonzero(result.success) > 0: result.retime_trajectory( plan_config.time_dilation_factor, interpolation_kind=self.finetune_trajopt_solver.interpolation_type, ) result.total_time = time.time() - start_time return result def _plan_batch_attempts( self, solve_state: ReacherSolveState, start_state: JointState, goal_pose: Pose, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), link_poses: Optional[Dict[str, Pose]] = None, ): """Plan batch attempts for a given reacher solve state. Args: solve_state: Reacher solve state for planning. start_state: Start joint state for planning. goal_pose: Goal pose to reach for end-effector. plan_config: Planning parameters for motion generation. link_poses: Goal poses for other links in the robot. Returns: MotionGenResult: Result of batched planning. """ start_time = time.time() goal_pose = goal_pose.clone() if plan_config.pose_cost_metric is not None: valid_query = self.update_pose_cost_metric( plan_config.pose_cost_metric, start_state, goal_pose ) if not valid_query: result = MotionGenResult( success=torch.as_tensor( [False for _ in solve_state.batch_size], device=self.motion_gen.tensor_args.device, ), valid_query=valid_query, status="Invalid Hold Partial Pose", ) return result if solve_state.batch_env: if solve_state.batch_size > self.world_coll_checker.n_envs: log_error("Batch Env is less that goal batch") if plan_config.enable_graph: log_error("Graph Search / Geometric Planner not supported in batch_env mode") if plan_config.enable_graph or ( plan_config.enable_graph_attempt is not None and plan_config.max_attempts >= plan_config.enable_graph_attempt ): log_warn("Batch mode enable graph is only supported with num_graph_seeds==1") plan_config.num_trajopt_seeds = 1 plan_config.num_graph_seeds = 1 solve_state.num_trajopt_seeds = 1 solve_state.num_graph_seeds = 1 self.update_batch_size(seeds=solve_state.num_trajopt_seeds, batch=solve_state.batch_size) ik_fail_count = 0 force_graph = plan_config.enable_graph partial_ik = plan_config.partial_ik_opt time_dict = { "solve_time": 0, "ik_time": 0, "graph_time": 0, "trajopt_time": 0, "trajopt_attempts": 0, } best_result = None for n in range(plan_config.max_attempts): result = self._plan_from_solve_state_batch( solve_state, start_state, goal_pose, plan_config, link_poses=link_poses, ) time_dict["solve_time"] += result.solve_time time_dict["ik_time"] += result.ik_time time_dict["graph_time"] += result.graph_time time_dict["trajopt_time"] += result.trajopt_time time_dict["trajopt_attempts"] += result.trajopt_attempts # if not all have succeeded, store the successful ones and re attempt: # TODO: update stored based on error if best_result is None: best_result = result.clone() else: # get success idx: idx = torch.nonzero(result.success).reshape(-1) if len(idx) > 0: best_result.copy_idx(idx, result) if ( result.status == MotionGenStatus.IK_FAIL and plan_config.ik_fail_return is not None ): # IF IK fails the first time, we exit assuming the goal is not reachable ik_fail_count += 1 if ik_fail_count > plan_config.ik_fail_return: break if ( torch.count_nonzero(best_result.success) >= goal_pose.batch * plan_config.success_ratio ): # we want 90% targets to succeed best_result.status = None break if plan_config.enable_graph_attempt is not None and ( n >= plan_config.enable_graph_attempt - 1 and result.status != MotionGenStatus.IK_FAIL and not plan_config.enable_graph ): plan_config.enable_graph = True plan_config.partial_ik_opt = False if plan_config.disable_graph_attempt is not None and ( n >= plan_config.disable_graph_attempt - 1 and result.status in [MotionGenStatus.TRAJOPT_FAIL, MotionGenStatus.GRAPH_FAIL] and not force_graph ): plan_config.enable_graph = False plan_config.partial_ik_opt = partial_ik if plan_config.fail_on_invalid_query: if not result.valid_query: best_result.valid_query = False best_result.status = "Invalid Problem" break if time.time() - start_time > plan_config.timeout: break best_result.solve_time = time_dict["solve_time"] best_result.ik_time = time_dict["ik_time"] best_result.graph_time = time_dict["graph_time"] best_result.trajopt_time = time_dict["trajopt_time"] best_result.trajopt_attempts = time_dict["trajopt_attempts"] best_result.attempts = n + 1 torch.cuda.synchronize(device=self.tensor_args.device) if plan_config.pose_cost_metric is not None: self.update_pose_cost_metric(PoseCostMetric.reset_metric()) if plan_config.time_dilation_factor is not None and torch.count_nonzero(result.success) > 0: result.retime_trajectory( plan_config.time_dilation_factor, interpolation_kind=self.finetune_trajopt_solver.interpolation_type, ) best_result.total_time = time.time() - start_time return best_result def _plan_from_solve_state( self, solve_state: ReacherSolveState, start_state: JointState, goal_pose: Pose, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), link_poses: Optional[Dict[str, Pose]] = None, ) -> MotionGenResult: """Plan from a given reacher solve state. Args: solve_state: Reacher solve state for planning. start_state: Start joint state for planning. goal_pose: Goal pose to reach for end-effector. plan_config: Planning parameters for motion generation. link_poses: Goal poses for other links in the robot. Returns: MotionGenResult: Result of planning. """ trajopt_seed_traj = None trajopt_seed_success = None trajopt_newton_iters = None graph_success = 0 if len(start_state.shape) == 1: log_error("Joint state should be not a vector (dof) should be (bxdof)") if goal_pose.shape[0] != 1: log_error( "Goal position should be of shape [1, n_goalset, -1], current shape: " + str(goal_pose.shape) ) # plan ik: ik_result = self._solve_ik_from_solve_state( goal_pose, solve_state, start_state, plan_config.use_nn_ik_seed, plan_config.partial_ik_opt, link_poses, ) if not plan_config.enable_graph and plan_config.partial_ik_opt: ik_result.success[:] = True # check for success: result = MotionGenResult( ik_result.success.view(-1)[0:1], # This is not true for batch mode ik_time=ik_result.solve_time, solve_time=ik_result.solve_time, ) if self.store_debug_in_result: result.debug_info = {"ik_result": ik_result} ik_success = torch.count_nonzero(ik_result.success) if ik_success == 0: result.status = MotionGenStatus.IK_FAIL return result # do graph search: with profiler.record_function("motion_gen/post_ik"): ik_out_seeds = solve_state.num_trajopt_seeds if plan_config.enable_graph: ik_out_seeds = min(solve_state.num_trajopt_seeds, ik_success) goal_config = ik_result.solution[ik_result.success].view(-1, self.ik_solver.dof)[ :ik_success ] start_config = tensor_repeat_seeds(start_state.position, ik_out_seeds) if plan_config.enable_opt: self._trajopt_goal_config[:] = ik_result.solution # do graph search: if plan_config.enable_graph: interpolation_steps = None if plan_config.enable_opt: interpolation_steps = self.trajopt_solver.action_horizon log_info("MG: running GP") graph_result = self.graph_search(start_config, goal_config, interpolation_steps) trajopt_seed_success = graph_result.success graph_success = torch.count_nonzero(graph_result.success).item() result.graph_time = graph_result.solve_time result.solve_time += graph_result.solve_time if graph_success > 0: log_info("MG: GP Success") result.graph_plan = graph_result.interpolated_plan result.interpolated_plan = graph_result.interpolated_plan result.used_graph = True if plan_config.enable_opt: trajopt_seed = ( result.graph_plan.position.view( 1, # solve_state.batch_size, graph_success, # solve_state.num_trajopt_seeds, interpolation_steps, self._dof, ) .transpose(0, 1) .contiguous() ) trajopt_seed_traj = torch.zeros( (trajopt_seed.shape[0], 1, self.trajopt_solver.action_horizon, self._dof), device=self.tensor_args.device, dtype=self.tensor_args.dtype, ) trajopt_seed_traj[:, :, :interpolation_steps, :] = trajopt_seed trajopt_seed_success = ik_result.success.clone() trajopt_seed_success[ik_result.success] = graph_result.success trajopt_seed_success = trajopt_seed_success.view( solve_state.batch_size, solve_state.num_trajopt_seeds ) trajopt_newton_iters = self.graph_trajopt_iters else: _, idx = torch.topk( graph_result.path_length[graph_result.success], k=1, largest=False ) result.interpolated_plan = result.interpolated_plan[idx].squeeze(0) result.optimized_dt = self.tensor_args.to_device(self.interpolation_dt) result.optimized_plan = result.interpolated_plan[ : graph_result.path_buffer_last_tstep[idx.item()] ] idx = idx.view(-1) + self._batch_col result.position_error = ik_result.position_error[ik_result.success][ graph_result.success ][idx] result.rotation_error = ik_result.rotation_error[ik_result.success][ graph_result.success ][idx] result.path_buffer_last_tstep = graph_result.path_buffer_last_tstep[ idx.item() : idx.item() + 1 ] result.success = result.success.view(-1)[0:1] result.success[:] = True return result else: result.success[:] = False result.status = MotionGenStatus.GRAPH_FAIL if not graph_result.valid_query: result.valid_query = False if self.store_debug_in_result: result.debug_info["graph_debug"] = graph_result.debug_info return result if plan_config.need_graph_success: return result # do trajopt:: if plan_config.enable_opt: with profiler.record_function("motion_gen/setup_trajopt_seeds"): self._trajopt_goal_config[:, :ik_success] = goal_config retract_config = None if plan_config.use_start_state_as_retract: retract_config = start_state.position.clone() goal = Goal( goal_pose=goal_pose, current_state=start_state, links_goal_pose=link_poses, retract_state=retract_config, ) if ( trajopt_seed_traj is None or graph_success < solve_state.num_trajopt_seeds * self.noisy_trajopt_seeds ): goal_config = self._trajopt_goal_config[0] # batch index == 0 goal_state = JointState.from_position( goal_config, ) seed_link_poses = None if link_poses is not None: seed_link_poses = {} for k in link_poses.keys(): seed_link_poses[k] = link_poses[k].repeat_seeds( solve_state.num_trajopt_seeds ) if goal_pose.shape[0] != 1: log_error( "Batch size of goal pose should be 1, current shape: " + str(goal_pose.shape) ) seed_goal = Goal( goal_pose=goal_pose.repeat_seeds(solve_state.num_trajopt_seeds), current_state=start_state.repeat_seeds(solve_state.num_trajopt_seeds), goal_state=goal_state, links_goal_pose=seed_link_poses, ) if trajopt_seed_traj is not None: trajopt_seed_traj = trajopt_seed_traj.transpose(0, 1).contiguous() # batch, num_seeds, h, dof if ( trajopt_seed_success.shape[1] < solve_state.num_trajopt_seeds * self.noisy_trajopt_seeds ): trajopt_seed_success_new = torch.zeros( (1, solve_state.num_trajopt_seeds * self.noisy_trajopt_seeds), device=self.tensor_args.device, dtype=torch.bool, ) trajopt_seed_success_new[0, : trajopt_seed_success.shape[1]] = ( trajopt_seed_success ) trajopt_seed_success = trajopt_seed_success_new # create seeds here: trajopt_seed_traj = self.trajopt_solver.get_seed_set( seed_goal, trajopt_seed_traj, # batch, num_seeds, h, dof num_seeds=self.noisy_trajopt_seeds, batch_mode=solve_state.batch_mode, seed_success=trajopt_seed_success, ) trajopt_seed_traj = trajopt_seed_traj.view( solve_state.num_trajopt_seeds * self.noisy_trajopt_seeds, solve_state.batch_size, self.trajopt_solver.action_horizon, self._dof, ).contiguous() if plan_config.enable_finetune_trajopt: og_value = self.trajopt_solver.interpolation_type self.trajopt_solver.interpolation_type = InterpolateType.LINEAR_CUDA with profiler.record_function("motion_gen/trajopt"): log_info("MG: running TO") traj_result = self._solve_trajopt_from_solve_state( goal, solve_state, trajopt_seed_traj, num_seeds_override=solve_state.num_trajopt_seeds * self.noisy_trajopt_seeds, newton_iters=trajopt_newton_iters, return_all_solutions=plan_config.parallel_finetune and plan_config.enable_finetune_trajopt, ) if plan_config.enable_finetune_trajopt: self.trajopt_solver.interpolation_type = og_value if self.store_debug_in_result: result.debug_info["trajopt_result"] = traj_result # run finetune if plan_config.enable_finetune_trajopt and torch.count_nonzero(traj_result.success) > 0: with profiler.record_function("motion_gen/finetune_trajopt"): seed_traj = traj_result.raw_action.clone() # solution.position.clone() seed_traj = seed_traj.contiguous() og_solve_time = traj_result.solve_time seed_override = 1 opt_dt = traj_result.optimized_dt if plan_config.parallel_finetune: opt_dt = torch.min(opt_dt[traj_result.success]) seed_override = solve_state.num_trajopt_seeds * self.noisy_trajopt_seeds finetune_time = 0 newton_iters = None for k in range(plan_config.finetune_attempts): scaled_dt = torch.clamp( opt_dt * plan_config.finetune_dt_scale * (plan_config.finetune_dt_decay ** (k)), self.trajopt_solver.minimum_trajectory_dt, ) if self.optimize_dt: self.finetune_trajopt_solver.update_solver_dt(scaled_dt.item()) traj_result = self._solve_trajopt_from_solve_state( goal, solve_state, seed_traj, trajopt_instance=self.finetune_trajopt_solver, num_seeds_override=seed_override, newton_iters=newton_iters, ) finetune_time += traj_result.solve_time if torch.count_nonzero(traj_result.success) > 0 or not self.optimize_dt: break seed_traj = traj_result.optimized_seeds.detach().clone() newton_iters = 4 traj_result.solve_time = finetune_time result.finetune_time = traj_result.solve_time traj_result.solve_time = og_solve_time if self.store_debug_in_result: result.debug_info["finetune_trajopt_result"] = traj_result elif plan_config.enable_finetune_trajopt: traj_result.success = traj_result.success[0:1] # if torch.count_nonzero(result.success) == 0: result.status = MotionGenStatus.TRAJOPT_FAIL result.solve_time += traj_result.solve_time + result.finetune_time result.trajopt_time = traj_result.solve_time result.trajopt_attempts = 1 result.success = traj_result.success if plan_config.enable_finetune_trajopt and torch.count_nonzero(result.success) == 0: result.status = MotionGenStatus.FINETUNE_TRAJOPT_FAIL result.interpolated_plan = traj_result.interpolated_solution.trim_trajectory( 0, traj_result.path_buffer_last_tstep[0] ) result.interpolation_dt = self.trajopt_solver.interpolation_dt result.path_buffer_last_tstep = traj_result.path_buffer_last_tstep result.position_error = traj_result.position_error result.rotation_error = traj_result.rotation_error result.optimized_dt = traj_result.optimized_dt result.optimized_plan = traj_result.solution result.goalset_index = traj_result.goalset_index return result def _plan_js_from_solve_state( self, solve_state: ReacherSolveState, start_state: JointState, goal_state: JointState, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), ) -> MotionGenResult: """Plan from a given reacher solve state for joint state. Args: solve_state: Reacher solve state for planning. start_state: Start joint state for planning. goal_state: Goal joint state to reach. plan_config: Planning parameters for motion generation. Returns: MotionGenResult: Result of planning. """ trajopt_seed_traj = None trajopt_seed_success = None trajopt_newton_iters = self.js_trajopt_solver.newton_iters graph_success = 0 if len(start_state.shape) == 1: log_error("Joint state should be not a vector (dof) should be (bxdof)") result = MotionGenResult(cspace_error=torch.zeros((1), device=self.tensor_args.device)) if self.store_debug_in_result: result.debug_info = {} # do graph search: if plan_config.enable_graph: start_config = torch.zeros( (solve_state.num_graph_seeds, self.js_trajopt_solver.dof), device=self.tensor_args.device, dtype=self.tensor_args.dtype, ) goal_config = start_config.clone() start_config[:] = start_state.position goal_config[:] = goal_state.position interpolation_steps = None if plan_config.enable_opt: interpolation_steps = self.js_trajopt_solver.action_horizon log_info("MG: running GP") graph_result = self.graph_search(start_config, goal_config, interpolation_steps) trajopt_seed_success = graph_result.success graph_success = torch.count_nonzero(graph_result.success).item() result.graph_time = graph_result.solve_time result.solve_time += graph_result.solve_time if graph_success > 0: result.graph_plan = graph_result.interpolated_plan result.interpolated_plan = graph_result.interpolated_plan result.used_graph = True if plan_config.enable_opt: trajopt_seed = ( result.graph_plan.position.view( 1, # solve_state.batch_size, graph_success, # solve_state.num_trajopt_seeds, interpolation_steps, self._dof, ) .transpose(0, 1) .contiguous() ) trajopt_seed_traj = torch.zeros( (trajopt_seed.shape[0], 1, self.trajopt_solver.action_horizon, self._dof), device=self.tensor_args.device, dtype=self.tensor_args.dtype, ) trajopt_seed_traj[:, :, :interpolation_steps, :] = trajopt_seed trajopt_seed_success = graph_result.success trajopt_seed_success = trajopt_seed_success.view( 1, solve_state.num_trajopt_seeds ) trajopt_newton_iters = self.graph_trajopt_iters else: _, idx = torch.topk( graph_result.path_length[graph_result.success], k=1, largest=False ) result.interpolated_plan = result.interpolated_plan[idx].squeeze(0) result.optimized_dt = self.tensor_args.to_device(self.interpolation_dt) result.optimized_plan = result.interpolated_plan[ : graph_result.path_buffer_last_tstep[idx.item()] ] idx = idx.view(-1) + self._batch_col result.cspace_error = torch.zeros((1), device=self.tensor_args.device) result.path_buffer_last_tstep = graph_result.path_buffer_last_tstep[ idx.item() : idx.item() + 1 ] result.success = torch.as_tensor([True], device=self.tensor_args.device) return result else: result.success = torch.as_tensor([False], device=self.tensor_args.device) result.status = MotionGenStatus.GRAPH_FAIL if not graph_result.valid_query: result.valid_query = False if self.store_debug_in_result: result.debug_info["graph_debug"] = graph_result.debug_info return result if plan_config.need_graph_success: return result # do trajopt: if plan_config.enable_opt: with profiler.record_function("motion_gen/setup_trajopt_seeds"): goal = Goal( current_state=start_state, goal_state=goal_state, ) if trajopt_seed_traj is None or graph_success < solve_state.num_trajopt_seeds * 1: seed_goal = Goal( current_state=start_state.repeat_seeds(solve_state.num_trajopt_seeds), goal_state=goal_state.repeat_seeds(solve_state.num_trajopt_seeds), ) if trajopt_seed_traj is not None: trajopt_seed_traj = trajopt_seed_traj.transpose(0, 1).contiguous() # batch, num_seeds, h, dof if trajopt_seed_success.shape[1] < self.js_trajopt_solver.num_seeds: trajopt_seed_success_new = torch.zeros( (1, solve_state.num_trajopt_seeds), device=self.tensor_args.device, dtype=torch.bool, ) trajopt_seed_success_new[0, : trajopt_seed_success.shape[1]] = ( trajopt_seed_success ) trajopt_seed_success = trajopt_seed_success_new # create seeds here: trajopt_seed_traj = self.js_trajopt_solver.get_seed_set( seed_goal, trajopt_seed_traj, # batch, num_seeds, h, dof num_seeds=1, batch_mode=False, seed_success=trajopt_seed_success, ) trajopt_seed_traj = ( trajopt_seed_traj.view( self.js_trajopt_solver.num_seeds * 1, 1, self.trajopt_solver.action_horizon, self._dof, ) .contiguous() .clone() ) if plan_config.enable_finetune_trajopt: og_value = self.trajopt_solver.interpolation_type self.js_trajopt_solver.interpolation_type = InterpolateType.LINEAR_CUDA with profiler.record_function("motion_gen/trajopt"): log_info("MG: running TO") traj_result = self._solve_trajopt_from_solve_state( goal, solve_state, trajopt_seed_traj, num_seeds_override=solve_state.num_trajopt_seeds, newton_iters=trajopt_newton_iters, return_all_solutions=plan_config.enable_finetune_trajopt, trajopt_instance=self.js_trajopt_solver, ) if plan_config.enable_finetune_trajopt: self.trajopt_solver.interpolation_type = og_value if self.store_debug_in_result: result.debug_info["trajopt_result"] = traj_result if torch.count_nonzero(traj_result.success) == 0: result.status = MotionGenStatus.TRAJOPT_FAIL # run finetune if plan_config.enable_finetune_trajopt and torch.count_nonzero(traj_result.success) > 0: with profiler.record_function("motion_gen/finetune_trajopt"): seed_traj = traj_result.raw_action.clone() og_solve_time = traj_result.solve_time opt_dt = traj_result.optimized_dt opt_dt = torch.min(opt_dt[traj_result.success]) finetune_time = 0 newton_iters = None for k in range(plan_config.finetune_attempts): scaled_dt = torch.clamp( opt_dt * plan_config.finetune_js_dt_scale * (plan_config.finetune_dt_decay ** (k)), self.js_trajopt_solver.minimum_trajectory_dt, ) if self.optimize_dt: self.finetune_js_trajopt_solver.update_solver_dt(scaled_dt.item()) traj_result = self._solve_trajopt_from_solve_state( goal, solve_state, seed_traj, trajopt_instance=self.finetune_js_trajopt_solver, num_seeds_override=solve_state.num_trajopt_seeds, newton_iters=newton_iters, return_all_solutions=False, ) finetune_time += traj_result.solve_time if torch.count_nonzero(traj_result.success) > 0 or not self.optimize_dt: break seed_traj = traj_result.optimized_seeds.detach().clone() newton_iters = 4 result.finetune_time = finetune_time traj_result.solve_time = og_solve_time if self.store_debug_in_result: result.debug_info["finetune_trajopt_result"] = traj_result if torch.count_nonzero(traj_result.success) == 0: result.status = MotionGenStatus.FINETUNE_TRAJOPT_FAIL elif plan_config.enable_finetune_trajopt: traj_result.success = traj_result.success[0:1] result.solve_time += traj_result.solve_time + result.finetune_time result.trajopt_time = traj_result.solve_time result.trajopt_attempts = 1 result.success = traj_result.success result.interpolated_plan = traj_result.interpolated_solution.trim_trajectory( 0, traj_result.path_buffer_last_tstep[0] ) result.interpolation_dt = self.trajopt_solver.interpolation_dt result.path_buffer_last_tstep = traj_result.path_buffer_last_tstep result.cspace_error = traj_result.cspace_error result.optimized_dt = traj_result.optimized_dt result.optimized_plan = traj_result.solution result.goalset_index = traj_result.goalset_index return result def _plan_from_solve_state_batch( self, solve_state: ReacherSolveState, start_state: JointState, goal_pose: Pose, plan_config: MotionGenPlanConfig = MotionGenPlanConfig(), link_poses: Optional[Dict[str, Pose]] = None, ) -> MotionGenResult: """Plan from a given reacher solve state in batch mode. Args: solve_state: Reacher solve state for planning. start_state: Start joint state for planning. goal_pose: Goal poses to reach for end-effector. plan_config: Planning parameters for motion generation. link_poses: Goal poses for other links in the robot. Returns: MotionGenResult: Result of planning. """ self._trajopt_goal_config[:] = self.get_retract_config().view(1, 1, self._dof) trajopt_seed_traj = None trajopt_seed_success = None trajopt_newton_iters = None graph_success = 0 # plan ik: ik_result = self._solve_ik_from_solve_state( goal_pose, solve_state, start_state, plan_config.use_nn_ik_seed, plan_config.partial_ik_opt, link_poses, ) if not plan_config.enable_graph and plan_config.partial_ik_opt: ik_result.success[:] = True # check for success: result = MotionGenResult( ik_result.success, position_error=ik_result.position_error, rotation_error=ik_result.rotation_error, ik_time=ik_result.solve_time, solve_time=ik_result.solve_time, debug_info={}, # goalset_index=ik_result.goalset_index, ) ik_success = torch.count_nonzero(ik_result.success) if ik_success == 0: result.status = MotionGenStatus.IK_FAIL result.success = result.success[:, 0] return result # do graph search: ik_out_seeds = solve_state.num_trajopt_seeds if plan_config.enable_graph: ik_out_seeds = min(solve_state.num_trajopt_seeds, ik_success) # if not plan_config.enable_opt and plan_config.enable_graph: # self.graph_planner.interpolation_steps = self.interpolation_steps # self.graph_planner.interpolation_type = self.interpolation_type # elif plan_config.enable_graph: # self.graph_planner.interpolation_steps = self.trajopt_solver.traj_tsteps # self.graph_planner.interpolation_type = InterpolateType.LINEAR goal_config = ik_result.solution[ik_result.success].view(-1, self.ik_solver.dof) # get shortest path if plan_config.enable_graph: interpolation_steps = None if plan_config.enable_opt: interpolation_steps = self.trajopt_solver.action_horizon start_graph_state = start_state.repeat_seeds(ik_out_seeds) start_config = start_graph_state.position[ik_result.success.view(-1)].view( -1, self.ik_solver.dof ) graph_result = self.graph_search(start_config, goal_config, interpolation_steps) graph_success = torch.count_nonzero(graph_result.success).item() result.graph_time = graph_result.solve_time result.solve_time += graph_result.solve_time if graph_success > 0: # path = graph_result.interpolated_plan result.graph_plan = graph_result.interpolated_plan result.interpolated_plan = graph_result.interpolated_plan result.used_graph = True if plan_config.enable_opt: trajopt_seed = result.graph_plan.position.view( graph_success, # solve_state.num_trajopt_seeds, interpolation_steps, self._dof, ).contiguous() trajopt_seed_traj = torch.zeros( (1, trajopt_seed.shape[0], self.trajopt_solver.action_horizon, self._dof), device=self.tensor_args.device, dtype=self.tensor_args.dtype, ) trajopt_seed_traj[0, :, :interpolation_steps, :] = trajopt_seed trajopt_seed_traj = trajopt_seed_traj.transpose(0, 1).contiguous() trajopt_seed_success = ik_result.success.clone() trajopt_seed_success[ik_result.success] = graph_result.success trajopt_seed_success = trajopt_seed_success.view( solve_state.num_trajopt_seeds, solve_state.batch_size ) trajopt_newton_iters = self.graph_trajopt_iters else: ik_success = ik_result.success.view(-1).clone() # only some might be successful: g_dim = torch.nonzero(ik_success).view(-1)[graph_result.success] self._batch_graph_search_buffer.copy_at_index( graph_result.interpolated_plan, g_dim ) # result.graph_plan = JointState.from_position( # self._batch_graph_search_buffer, # joint_names=graph_result.interpolated_plan.joint_names, # ) result.interpolated_plan = self._batch_graph_search_buffer g_dim = g_dim.cpu().squeeze().tolist() if isinstance(g_dim, int): g_dim = [g_dim] for x, x_val in enumerate(g_dim): self._batch_path_buffer_last_tstep[x_val] = ( graph_result.path_buffer_last_tstep[x] ) result.path_buffer_last_tstep = self._batch_path_buffer_last_tstep result.optimized_plan = result.interpolated_plan result.optimized_dt = torch.as_tensor( [ self.interpolation_dt for i in range(result.interpolated_plan.position.shape[0]) ], device=self.tensor_args.device, dtype=self.tensor_args.dtype, ) result.success = result.success.view(-1).clone() result.success[ik_success][graph_result.success] = True return result else: result.success[:] = False result.success = result.success[:, 0] result.status = MotionGenStatus.GRAPH_FAIL if not graph_result.valid_query: result.valid_query = False if self.store_debug_in_result: result.debug_info = {"graph_debug": graph_result.debug_info} return result if plan_config.enable_opt: # get goal configs based on ik success: self._trajopt_goal_config[ik_result.success] = goal_config goal_config = self._trajopt_goal_config # batch index == 0 goal = Goal( goal_pose=goal_pose, current_state=start_state, links_goal_pose=link_poses, ) # generate seeds: if trajopt_seed_traj is None or ( plan_config.enable_graph and graph_success < solve_state.batch_size ): seed_link_poses = None if link_poses is not None: seed_link_poses = {} for k in link_poses.keys(): seed_link_poses[k] = link_poses[k].repeat_seeds( solve_state.num_trajopt_seeds ) seed_goal = Goal( goal_pose=goal_pose.repeat_seeds(solve_state.num_trajopt_seeds), current_state=start_state.repeat_seeds(solve_state.num_trajopt_seeds), goal_state=JointState.from_position(goal_config.view(-1, self._dof)), links_goal_pose=seed_link_poses, ) if trajopt_seed_traj is not None: trajopt_seed_traj = trajopt_seed_traj.transpose(0, 1).contiguous() # create seeds here: trajopt_seed_traj = self.trajopt_solver.get_seed_set( seed_goal, trajopt_seed_traj, # batch, num_seeds, h, dof num_seeds=1, batch_mode=solve_state.batch_mode, seed_success=trajopt_seed_success, ) trajopt_seed_traj = trajopt_seed_traj.view( solve_state.num_trajopt_seeds, solve_state.batch_size, self.trajopt_solver.action_horizon, self._dof, ).contiguous() if plan_config.enable_finetune_trajopt: og_value = self.trajopt_solver.interpolation_type self.trajopt_solver.interpolation_type = InterpolateType.LINEAR_CUDA traj_result = self._solve_trajopt_from_solve_state( goal, solve_state, trajopt_seed_traj, newton_iters=trajopt_newton_iters, return_all_solutions=plan_config.enable_finetune_trajopt, ) # output of traj result will have 1 solution per batch # run finetune opt on 1 solution per batch: if plan_config.enable_finetune_trajopt: self.trajopt_solver.interpolation_type = og_value if self.store_debug_in_result: result.debug_info["trajopt_result"] = traj_result # run finetune if plan_config.enable_finetune_trajopt and torch.count_nonzero(traj_result.success) > 0: with profiler.record_function("motion_gen/finetune_trajopt"): seed_traj = traj_result.raw_action.clone() # solution.position.clone() seed_traj = seed_traj.contiguous() og_solve_time = traj_result.solve_time scaled_dt = torch.clamp( torch.max(traj_result.optimized_dt[traj_result.success]) * self.finetune_dt_scale, self.trajopt_solver.minimum_trajectory_dt, ) self.finetune_trajopt_solver.update_solver_dt(scaled_dt.item()) traj_result = self._solve_trajopt_from_solve_state( goal, solve_state, seed_traj, trajopt_instance=self.finetune_trajopt_solver, num_seeds_override=solve_state.num_trajopt_seeds, ) result.finetune_time = traj_result.solve_time traj_result.solve_time = og_solve_time if self.store_debug_in_result: result.debug_info["finetune_trajopt_result"] = traj_result elif plan_config.enable_finetune_trajopt and len(traj_result.success.shape) == 2: traj_result.success = traj_result.success[:, 0] result.success = traj_result.success result.interpolated_plan = traj_result.interpolated_solution result.solve_time += traj_result.solve_time result.trajopt_time = traj_result.solve_time result.position_error = traj_result.position_error result.rotation_error = traj_result.rotation_error result.cspace_error = traj_result.cspace_error result.goalset_index = traj_result.goalset_index result.path_buffer_last_tstep = traj_result.path_buffer_last_tstep result.optimized_plan = traj_result.solution result.optimized_dt = traj_result.optimized_dt if torch.count_nonzero(traj_result.success) == 0: result.status = MotionGenStatus.TRAJOPT_FAIL result.success[:] = False if self.store_debug_in_result: result.debug_info = {"trajopt_result": traj_result} return result def plan( self, start_state: JointState, goal_pose: Pose, enable_graph: bool = True, enable_opt: bool = True, use_nn_ik_seed: bool = False, need_graph_success: bool = False, max_attempts: int = 100, timeout: float = 10.0, enable_graph_attempt: Optional[int] = None, disable_graph_attempt: Optional[int] = None, trajopt_attempts: int = 1, ik_fail_return: Optional[int] = None, partial_ik_opt: bool = False, num_ik_seeds: Optional[int] = None, num_graph_seeds: Optional[int] = None, num_trajopt_seeds: Optional[int] = None, ): """Deprecated method. Use :meth:`MotionGen.plan_single` or others instead.""" log_warn("Deprecated method. Use MotionGen.plan_single or others instead.") plan_config = MotionGenPlanConfig( enable_graph, enable_opt, use_nn_ik_seed, need_graph_success, max_attempts, timeout, enable_graph_attempt, disable_graph_attempt, ik_fail_return, partial_ik_opt, num_ik_seeds, num_graph_seeds, num_trajopt_seeds, ) result = self.plan_single(start_state, goal_pose, plan_config) return result def batch_plan( self, start_state: JointState, goal_pose: Pose, enable_graph: bool = True, enable_opt: bool = True, use_nn_ik_seed: bool = False, need_graph_success: bool = False, max_attempts: int = 1, timeout: float = 10.0, enable_graph_attempt: Optional[int] = None, disable_graph_attempt: Optional[int] = None, success_ratio: float = 1.0, ik_fail_return: Optional[int] = None, fail_on_invalid_query: bool = False, partial_ik_opt: bool = False, num_ik_seeds: Optional[int] = None, num_graph_seeds: Optional[int] = None, num_trajopt_seeds: Optional[int] = None, ): """Deprecated method. Use :meth:`MotionGen.plan_batch` or others instead.""" log_warn("Deprecated method. Use MotionGen.plan_batch or others instead.") plan_config = MotionGenPlanConfig( enable_graph, enable_opt, use_nn_ik_seed, need_graph_success, max_attempts, timeout, enable_graph_attempt, disable_graph_attempt, ik_fail_return, partial_ik_opt, num_ik_seeds, num_graph_seeds, num_trajopt_seeds, success_ratio=success_ratio, fail_on_invalid_query=fail_on_invalid_query, ) result = self.plan_batch(start_state, goal_pose, plan_config) return result