Source code for mink_warp.limits.limit

"""Base class for hard kinematic limits enforced by the constrained solver.

A limit constrains the tangent step ``dq`` and can expose either or both of two
forms, matching mink's stacked ``G dq <= h`` inequalities:

* **box** — a per-dof interval, intersected into a shared per-world ``[lo, hi]``
  buffer via :meth:`Limit.apply_box` (``lo = max(lo, ...)``,
  ``hi = min(hi, ...)``). Single-dof limits compose by tightening. This is the
  fast default path (solved exactly at every ADMM step by the box kernel).

* **dense inequality** — general rows ``G dq <= h`` scattered into a shared
  padded ``(G, h)`` buffer via :meth:`Limit.scatter_inequalities`. Used by the
  general-inequality solve path, and the *only* form for constraints a per-dof
  box cannot express (an arbitrary half-space, collision avoidance, ...).

A limit advertises ``n_inequalities`` (how many dense rows it contributes; ``0``
means it has no dense form) and ``box_capable`` (whether :meth:`apply_box`
works). The built-in joint/velocity limits support both; a general half-space
limit is inequality-only.
"""

from __future__ import annotations

import abc

import warp as wp

from ..configuration import Configuration


[docs] class Limit(abc.ABC): r"""Abstract hard kinematic limit. Limits constrain the tangent step :math:`\Delta q \in T_q(\mathcal{C})` and are enforced by :class:`~mink_warp.ConstrainedSolver`. Each limit may expose: * **box** — per-dof bounds :math:`\ell \leq \Delta q \leq u` * **dense rows** — :math:`G(q)\, \Delta q \leq h(q)` """ #: Number of dense ``G dq <= h`` rows this limit contributes (0 = box-only). n_inequalities: int = 0 #: Whether :meth:`apply_box` is implemented (False for inequality-only limits). box_capable: bool = True #: False when inequality assembly reads device state on the host each step. supports_cuda_graph: bool = True
[docs] def apply_box( self, configuration: Configuration, dt: float, lo: wp.array, hi: wp.array, ) -> None: """Intersect this limit's bounds into the shared per-world box. Args: configuration: Current batched configuration. dt: Integration timestep [s]. lo: Per-world lower bound on ``dq``, shape ``(nworld, nv)``, updated in place with ``lo = max(lo, this_lower)``. hi: Per-world upper bound on ``dq``, shape ``(nworld, nv)``, updated in place with ``hi = min(hi, this_upper)``. """ raise NotImplementedError( f"{type(self).__name__} is inequality-only (box_capable=False); " f"it has no per-dof box form." )
[docs] def scatter_inequalities( self, configuration: Configuration, dt: float, row_offset: int, G: wp.array, h: wp.array, ) -> None: r"""Write dense inequality rows into shared buffers. Row :math:`i` encodes :math:`G_i \Delta q \leq h_i`. Buffers have shape ``(nworld, m, nv)`` and ``(nworld, m)``; only rows owned by this limit are overwritten. Args: configuration: Current batched configuration. dt: Timestep :math:`\mathrm{d}t` in [s]. row_offset: First row index for this limit. G: Shared inequality matrix. h: Shared bound vector. """ raise NotImplementedError( f"{type(self).__name__} declares n_inequalities=" f"{self.n_inequalities} but does not implement scatter_inequalities." )