4092 lines
183 KiB
Python
4092 lines
183 KiB
Python
#
|
|
# 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
|
|
|
|
<p>
|
|
<video autoplay="True" loop="True" muted="True" preload="auto" width="100%"><source src="../videos/ur10_real_timer.mp4" type="video/mp4"></video>
|
|
</p>
|
|
|
|
|
|
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
|