Source code for isaaclab_newton.controllers.differential_ik

# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

from __future__ import annotations

from typing import TYPE_CHECKING

import torch
import warp as wp
from newton.controllers import ControllerDifferentialIKModelFree, DifferentialIKMethod

from .utils import bind_inputs, spatial_vector

if TYPE_CHECKING:
    from .differential_ik_cfg import NewtonDifferentialIKControllerCfg


[docs] class NewtonDifferentialIKController: """Differential inverse kinematics through Newton's model-free solver. Wraps :class:`newton.controllers.ControllerDifferentialIKModelFree` for batched torch inputs. The caller provides the end-effector pose and Jacobian, so the controller works with any physics backend. Poses, targets, and the Jacobian must share one frame, for example the robot root frame. Inputs are bound to Newton without copies when they are contiguous float32 tensors. The returned joint targets are a view of Newton's output buffer and are overwritten by the next :meth:`compute`. """
[docs] def __init__(self, cfg: NewtonDifferentialIKControllerCfg, num_envs: int, num_joints: int, device: str): """Initialize the controller. Args: cfg: The controller configuration. num_envs: The number of environments. num_joints: The number of controlled joints per environment. device: The device to use for computations. """ self.cfg = cfg self.num_envs = num_envs self.num_joints = num_joints self._device = device # wide placeholder limits keep avoidance inactive until set_joint_pos_limits() provides real ones num_dofs = num_envs * num_joints use_limits = cfg.use_joint_limit_avoidance self._controller = ControllerDifferentialIKModelFree( controlled_dofs_per_robot=wp.full(num_envs, num_joints, dtype=wp.int32, device=device), axis_weight=spatial_vector(cfg.axis_weight), bandwidth=cfg.bandwidth, damping=cfg.damping, ik_method=DifferentialIKMethod(cfg.ik_method), adaptive_damping_min=cfg.adaptive_damping_min, adaptive_damping_max=cfg.adaptive_damping_max, adaptive_damping_threshold=cfg.adaptive_damping_threshold, truncated_svd_threshold=cfg.truncated_svd_threshold, use_joint_limit_avoidance=use_limits, joint_limit_avoidance_gain=cfg.joint_limit_avoidance_gain, joint_limit_avoidance_margin=cfg.joint_limit_avoidance_margin, joint_pos_lower=wp.full(num_dofs, -1.0e9, dtype=wp.float32, device=device) if use_limits else None, joint_pos_upper=wp.full(num_dofs, 1.0e9, dtype=wp.float32, device=device) if use_limits else None, use_null_space_posture_control=cfg.use_null_space_posture_control, null_space_stiffness=cfg.null_space_stiffness, null_space_damping=cfg.null_space_damping, null_space_axes=spatial_vector(cfg.null_space_axes), device=device, ) self._inputs = self._controller.input() self._outputs = self._controller.output() self._joint_pos_des = wp.to_torch(self._outputs.joint_q_target).view(num_envs, num_joints)
@property def newton_controller(self) -> ControllerDifferentialIKModelFree: """The wrapped Newton controller.""" return self._controller
[docs] def set_joint_pos_limits(self, lower: torch.Tensor, upper: torch.Tensor) -> None: """Set the joint position limits used by joint-limit avoidance. Args: lower: Lower joint-position limits [m or rad, depending on joint type], shape (num_joints,) or (num_envs, num_joints). upper: Upper joint-position limits [m or rad, depending on joint type], same shape as ``lower``. """ shape = (self.num_envs, self.num_joints) lower = torch.broadcast_to(lower.to(self._device, torch.float32), shape).reshape(-1).contiguous() upper = torch.broadcast_to(upper.to(self._device, torch.float32), shape).reshape(-1).contiguous() self._controller.set_joint_limits(joint_pos_lower=wp.from_torch(lower), joint_pos_upper=wp.from_torch(upper))
[docs] def compute( self, ee_pose: torch.Tensor, ee_pose_des: torch.Tensor, jacobian: torch.Tensor, joint_pos: torch.Tensor, dt: float, *, bandwidth: torch.Tensor | None = None, damping: torch.Tensor | None = None, null_space_joint_pos_target: torch.Tensor | None = None, null_space_stiffness: torch.Tensor | None = None, null_space_damping: torch.Tensor | None = None, ) -> torch.Tensor: """Compute the one-step-ahead joint position targets. Keyword inputs are only accepted when the configuration leaves the matching gain live or enables the matching feature. ``None`` keeps the previously bound tensor, or zeros if none was bound. Args: ee_pose: Current end-effector pose ``(x, y, z, qx, qy, qz, qw)`` [m, unitless], shape (num_envs, 7). ee_pose_des: Desired end-effector pose [m, unitless], shape (num_envs, 7). jacobian: End-effector Jacobian, shape (num_envs, 6, num_joints). joint_pos: Current joint positions [m or rad, depending on joint type], shape (num_envs, num_joints). dt: Step duration [s]. bandwidth: Output velocity gain [1/s], shape (num_envs, num_joints). damping: Damped-least-squares regularization, shape (num_envs,). null_space_joint_pos_target: Posture target [m or rad, depending on joint type], shape (num_envs, num_joints). null_space_stiffness: Posture-control gain, shape (num_envs, num_joints). null_space_damping: Null-space projector regularization, shape (num_envs,). Returns: The joint position targets [m or rad, depending on joint type], shape (num_envs, num_joints). """ bind_inputs( self._inputs, tool_pose_world=ee_pose, desired_tool_pose_world=ee_pose_des, jacobian_tool_world=jacobian, joint_q=joint_pos, bandwidth=bandwidth, damping=damping, q_des_null=null_space_joint_pos_target, null_space_stiffness=null_space_stiffness, null_space_damping=null_space_damping, ) self._controller.step(inputs=self._inputs, outputs=self._outputs, dt=dt) return self._joint_pos_des