Source code for mink_warp.tasks.relative_frame_task
"""Relative 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,
relative_frame_task_error_jacobian,
relative_pose_wxyz_xyz,
)
from ..lie import SE3
from .task import TargetedTask
[docs]
class RelativeFrameTask(TargetedTask):
r"""Regulate a frame pose relative to a root frame.
The target is a rigid transform :math:`T_{tr}` (frame :math:`t` expressed in
root :math:`r`). With current relative pose :math:`T_{br}`:
.. math::
e(q) = \log(T_{bt}) \quad \text{with} \quad T_{bt} = T_{br}\, T_{tr}^{-1}
The Jacobian accounts for both the regulated frame and the root frame
(chain rule on :math:`SE(3)`), matching Mink's ``RelativeFrameTask``.
Costs use the same units as :class:`FrameTask`:
:math:`[\mathrm{cost}] / [\mathrm{m}]` and
:math:`[\mathrm{cost}] / [\mathrm{rad}]`.
"""
k: int = 6
target_width: int = 7
def __init__(
self,
frame_name: str,
frame_type: str,
root_name: str,
root_type: str,
position_cost: npt.ArrayLike,
orientation_cost: npt.ArrayLike,
gain: float = 1.0,
lm_damping: float = 0.0,
):
r"""Define a relative frame task.
Args:
frame_name: Regulated frame name.
frame_type: ``"body"``, ``"geom"``, or ``"site"``.
root_name: Reference frame name.
root_type: Reference frame type.
position_cost: In :math:`[\mathrm{cost}] / [\mathrm{m}]`.
orientation_cost: In :math:`[\mathrm{cost}] / [\mathrm{rad}]`.
gain: Task gain :math:`\alpha \in [0, 1]`.
lm_damping: LM damping scale.
"""
super().__init__(cost=np.zeros(6), gain=gain, lm_damping=lm_damping)
self.frame_name = frame_name
self.frame_type = frame_type
self.root_name = root_name
self.root_type = root_type
self._frame_pose: wp.array | None = None
self._root_pose: wp.array | None = None
self._rel_pose: wp.array | None = None
self._jac_frame: wp.array | None = None
self._jac_root: 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 "
f"{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_root: wp.array | SE3 | npt.ArrayLike,
*,
configuration: Configuration | None = None,
) -> None:
if isinstance(transform_target_to_root, SE3):
transform_target_to_root = transform_target_to_root.wxyz_xyz
self._set_pending(transform_target_to_root, configuration=configuration)
[docs]
def set_target_from_configuration(self, configuration: Configuration) -> None:
pose = configuration.get_transform(
self.frame_name,
self.frame_type,
self.root_name,
self.root_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)
nworld = configuration.nworld
nv = configuration.nv
self._frame_pose = wp.zeros((nworld, 7), dtype=float)
self._root_pose = wp.zeros((nworld, 7), dtype=float)
self._rel_pose = wp.zeros((nworld, 7), dtype=float)
self._jac_frame = wp.zeros((nworld, 6, nv), dtype=float)
self._jac_root = wp.zeros((nworld, 6, nv), dtype=float)
def _eval(self, configuration: Configuration) -> None:
target = self._require_target(configuration)
assert self._frame_pose is not None
assert self._root_pose is not None
assert self._rel_pose is not None
assert self._jac_frame is not None
assert self._jac_root is not None
assert self._error is not None
assert self._jacobian is not None
frame_world = configuration.get_transform_frame_to_world(
self.frame_name, self.frame_type
)
with wp.ScopedDevice(configuration.device):
wp.launch(
copy_poses,
dim=configuration.nworld,
inputs=[frame_world],
outputs=[self._frame_pose],
)
root_world = configuration.get_transform_frame_to_world(
self.root_name, self.root_type
)
jac_frame = configuration.get_frame_jacobian(
self.frame_name, self.frame_type
)
with wp.ScopedDevice(configuration.device):
wp.copy(self._jac_frame, jac_frame)
jac_root = configuration.get_frame_jacobian(
self.root_name, self.root_type
)
with wp.ScopedDevice(configuration.device):
wp.launch(
copy_poses,
dim=configuration.nworld,
inputs=[root_world],
outputs=[self._root_pose],
)
wp.copy(self._jac_root, jac_root)
wp.launch(
relative_pose_wxyz_xyz,
dim=configuration.nworld,
inputs=[self._root_pose, self._frame_pose],
outputs=[self._rel_pose],
)
wp.launch(
relative_frame_task_error_jacobian,
dim=configuration.nworld,
inputs=[
target,
self._rel_pose,
self._jac_frame,
self._jac_root,
configuration.nv,
],
outputs=[self._error, self._jacobian],
)