Skip to content

[Bug]: MPCSolver.update_goal_tool_poses: IK failure blocks the Cartesian goal update too, not just joint-position tracking #711

Description

@will-44

Prerequisites

  • I have searched existing issues and discussions and could not find a duplicate.
  • I have read the relevant section of the documentation.
  • I can reproduce this on the latest main (or the most recent release).

Bug summary

Context: Reactive control (optimize_next_action) on a 6-DOF non-redundant arm (Doosan M1013), calling update_goal_tool_poses(goal, run_ik=True) once per goal-set/retarget.
Observed: In solver_mpc.py, update_goal_tool_poses's run_ik=True branch gates everything — including the plain Cartesian goal update — behind IK success:

       if run_ik:
           ik_result = self._solve_ik_for_goal(goal_tool_poses, robot_ids)
           if ik_result is not None:
               if use_best_effort_ik or torch.all(ik_result.success):
                   goal_joint_state = JointState.from_position(
                       ik_result.solution.view(-1, self.action_dim), joint_names=self.joint_names
                   )
                   log_info("IK succeeded, updated target joint configuration with IK solution")
                   if use_ik_goal:
                       self.update_goal_state(goal_joint_state)
                       current_state = self.core._goal_buffer.current_js.clone()
                       self.enable_joint_position_tracking()
                   new_goal_buffer = self.goal_registry_manager.update_goal_tool_poses(goal_tool_poses)
                   self._goal_tool_poses = goal_tool_poses
                   self._update_rollout_params(new_goal_buffer)
                   return True
           return False

_solve_ik_for_goal seeds IKSolver.solve_pose with a single seed (the current joint state, return_seeds=1). With use_best_effort_ik=False (the default), if that single seed doesn't converge — which we hit in practice near a kinematic singularity — the whole call returns False and the Cartesian goal is silently never set, even though setting it doesn't actually require IK to succeed (the else branch proves that).

Steps to reproduce

curobo/curobo/_src/solver/solver_mpc.py l415-l431

Expected behavior

Question: Is this coupling intentional? It seems like IK failure should only skip the joint-position-tracking anchor (use_ik_goal's effect), not the Cartesian goal update itself — i.e. run_ik=True + failed IK could still fall through to the same Cartesian-only update the else branch does, rather than a hard no-op. Right now the only way to get a Cartesian goal applied and a best-effort joint anchor is use_best_effort_ik=True, which accepts a possibly-far-off, non-converged IK solution as a hard joint-tracking target — there doesn't seem to be a middle ground ("try to anchor joint-space, but always at least apply the Cartesian goal").

Secondary question: is a single current-state seed (return_seeds=1) expected to be reliable near a singularity, or would multiple seeds be recommended there even for a non-redundant 6-DOF arm? We're using the joint-position-tracking anchor specifically to damp null-space drift (J4/J6 co-rotating with no cost pushing back until a joint limit engages) near a wrist singularity, and want to understand whether single-seed IK is a good fit for that use case or if we should be seeding differently.

Actual behavior / error output

something like that (but cleanner)
        if run_ik:
            ik_result = self._solve_ik_for_goal(goal_tool_poses, robot_ids)
            if ik_result is not None:
                if use_best_effort_ik or torch.all(ik_result.success):
                    goal_joint_state = JointState.from_position(
                        ik_result.solution.view(-1, self.action_dim), joint_names=self.joint_names
                    )
                    log_info("IK succeeded, updated target joint configuration with IK solution")
                    if use_ik_goal:
                        self.update_goal_state(goal_joint_state)
                        current_state = self.core._goal_buffer.current_js.clone()
                        self.enable_joint_position_tracking()
                    new_goal_buffer = self.goal_registry_manager.update_goal_tool_poses(goal_tool_poses)
                    self._goal_tool_poses = goal_tool_poses
                    self._update_rollout_params(new_goal_buffer)
                    return True
                else:
                    self.disable_joint_position_tracking()
                    new_goal_buffer = self.goal_registry_manager.update_goal_tool_poses(goal_tool_poses)
                    self._goal_tool_poses = goal_tool_poses
                    self._update_rollout_params(new_goal_buffer)
            return False
        else:
            self.disable_joint_position_tracking()
            new_goal_buffer = self.goal_registry_manager.update_goal_tool_poses(goal_tool_poses)
            self._goal_tool_poses = goal_tool_poses
            self._update_rollout_params(new_goal_buffer)
            return True

cuRobo version + commit SHA

cuRobo v0.8.0.post1.dev42 (commit 8e734f3)

Installation method

Source — CUDA 12 + PyTorch (uv pip install .[cu12-torch])

Kernel backend

cuda_core (default, runtime compilation)

Python version

Python 3.12.3

PyTorch version (if installed)

2.8.0+cu128 12.8

GPU / driver / CUDA toolkit

cuda_12.0.r12.0/compiler.32267302_0

Operating system

ubuntu 24

Isaac Sim version (if applicable)

none

Additional context

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions