Source code for mink_warp.tasks.frame_task

"""Frame task (device-side, Mink formulas)."""

from __future__ import annotations

import numpy as np
import numpy.typing as npt
import warp as wp

from ..configuration import Configuration
from ..exceptions import TaskDefinitionError
from ..kernels.frame import (
    broadcast_pose,
    copy_poses,
    frame_task_error_jacobian,
)
from ..lie import SE3
from .task import TargetedTask


[docs] class FrameTask(TargetedTask): r"""Regulate the pose of a body, geom, or site in the world frame. The error is a body twist :math:`e(q) \in \mathbb{R}^6` (linear then angular) expressed in the regulated frame :math:`b`. With target frame :math:`t` and world frame :math:`0`: .. math:: e(q) := \log(T_{bt}) = \log(T_{b0}^{-1} T_{t0}) The Jacobian (Mink / Pink body-frame convention) is: .. math:: J(q) = -\mathrm{jlog}_6(T_{tb})\, {}_b J_{0b}(q) Costs homogenize SI units: ``position_cost`` is in :math:`[\mathrm{cost}] / [\mathrm{m}]`, ``orientation_cost`` in :math:`[\mathrm{cost}] / [\mathrm{rad}]`. A 1 cm position error at unit position cost weighs like a 1 rad error at unit orientation cost only when the costs are chosen accordingly. See Also: :class:`RelativeFrameTask` when the target is expressed in a mobile root frame rather than the world. """ k: int = 6 target_width: int = 7 def __init__( self, frame_name: str, frame_type: str, position_cost: npt.ArrayLike, orientation_cost: npt.ArrayLike, gain: float = 1.0, lm_damping: float = 0.0, ): r"""Define a frame task. Args: frame_name: MuJoCo frame name (body, geom, or site). frame_type: ``"body"``, ``"geom"``, or ``"site"``. position_cost: Weight on position error, in :math:`[\mathrm{cost}] / [\mathrm{m}]` (scalar or 3-vector). orientation_cost: Weight on orientation error, in :math:`[\mathrm{cost}] / [\mathrm{rad}]` (scalar or 3-vector). gain: Task gain :math:`\alpha \in [0, 1]`. lm_damping: LM damping scale (see :class:`Task`). """ super().__init__(cost=np.zeros(6), gain=gain, lm_damping=lm_damping) self.frame_name = frame_name self.frame_type = frame_type self._frame_pose: wp.array | None = None self.set_position_cost(position_cost) self.set_orientation_cost(orientation_cost)
[docs] def set_position_cost(self, position_cost: npt.ArrayLike) -> None: position_cost = np.atleast_1d(np.asarray(position_cost, dtype=np.float64)) if position_cost.ndim != 1 or position_cost.shape[0] not in (1, 3): raise TaskDefinitionError( f"position cost must be shape (1,) or (3,), got {position_cost.shape}" ) if not np.all(position_cost >= 0.0): raise TaskDefinitionError("position cost must be >= 0") self.cost[:3] = position_cost self._cost_dev = None
[docs] def set_orientation_cost(self, orientation_cost: npt.ArrayLike) -> None: orientation_cost = np.atleast_1d( np.asarray(orientation_cost, dtype=np.float64) ) if orientation_cost.ndim != 1 or orientation_cost.shape[0] not in (1, 3): raise TaskDefinitionError( f"orientation cost must be shape (1,) or (3,), got {orientation_cost.shape}" ) if not np.all(orientation_cost >= 0.0): raise TaskDefinitionError("orientation cost must be >= 0") self.cost[3:] = orientation_cost self._cost_dev = None
[docs] def set_target( self, transform_target_to_world: wp.array | SE3 | npt.ArrayLike, *, configuration: Configuration | None = None, ) -> None: if isinstance(transform_target_to_world, SE3): transform_target_to_world = transform_target_to_world.wxyz_xyz self._set_pending(transform_target_to_world, configuration=configuration)
[docs] def set_target_from_configuration(self, configuration: Configuration) -> None: pose = configuration.get_transform_frame_to_world( self.frame_name, self.frame_type ) self._ensure_buffers(configuration) assert self._target is not None with wp.ScopedDevice(configuration.device): wp.copy(self._target, pose) self._pending = None self._target_set = True
def _alloc_extra_buffers(self, configuration: Configuration) -> None: super()._alloc_extra_buffers(configuration) self._frame_pose = wp.zeros((configuration.nworld, 7), dtype=float) def _broadcast_target(self, src: wp.array, nworld: int) -> None: assert self._target is not None wp.launch(broadcast_pose, dim=nworld, inputs=[src], outputs=[self._target]) def _eval(self, configuration: Configuration) -> None: target = self._require_target(configuration) assert self._frame_pose is not None assert self._error is not None assert self._jacobian is not None frame_pose = configuration.get_transform_frame_to_world( self.frame_name, self.frame_type ) jac_body = configuration.get_frame_jacobian( self.frame_name, self.frame_type ) with wp.ScopedDevice(configuration.device): wp.launch( copy_poses, dim=configuration.nworld, inputs=[frame_pose], outputs=[self._frame_pose], ) wp.launch( frame_task_error_jacobian, dim=configuration.nworld, inputs=[target, self._frame_pose, jac_body, configuration.nv], outputs=[self._error, self._jacobian], )