mink 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mink/__init__.py CHANGED
@@ -25,7 +25,16 @@ from .limits import (
25
25
  VelocityLimit,
26
26
  )
27
27
  from .solve_ik import build_ik, solve_ik
28
- from .tasks import ComTask, FrameTask, Objective, PostureTask, TargetNotSet, Task
28
+ from .tasks import (
29
+ ComTask,
30
+ DampingTask,
31
+ FrameTask,
32
+ Objective,
33
+ PostureTask,
34
+ RelativeFrameTask,
35
+ TargetNotSet,
36
+ Task,
37
+ )
29
38
  from .utils import (
30
39
  custom_configuration_vector,
31
40
  get_body_geom_ids,
@@ -34,14 +43,14 @@ from .utils import (
34
43
  move_mocap_to_frame,
35
44
  )
36
45
 
37
- __version__ = "0.0.1"
38
-
39
46
  __all__ = (
40
47
  "ComTask",
41
48
  "Configuration",
42
49
  "build_ik",
43
50
  "solve_ik",
51
+ "DampingTask",
44
52
  "FrameTask",
53
+ "RelativeFrameTask",
45
54
  "PostureTask",
46
55
  "Task",
47
56
  "Objective",
mink/configuration.py CHANGED
@@ -1,3 +1,12 @@
1
+ """Configuration space of a robot model.
2
+
3
+ The :class:`Configuration` class encapsulates a MuJoCo
4
+ `model <https://mujoco.readthedocs.io/en/latest/APIreference/APItypes.html#mjmodel>`__
5
+ and `data <https://mujoco.readthedocs.io/en/latest/APIreference/APItypes.html#mjdata>`__,
6
+ offering easy access to frame transforms and frame Jacobians. A frame refers to a coordinate
7
+ system that can be attached to various parts of the robot, such as a body, geom, or site.
8
+ """
9
+
1
10
  from typing import Optional
2
11
 
3
12
  import mujoco
@@ -9,14 +18,19 @@ from .lie import SE3, SO3
9
18
 
10
19
 
11
20
  class Configuration:
12
- """A struct that provides convenient access to kinematic quantities such as frame
13
- transforms and frame jacobians. Frames can be defined at bodies, geoms or sites.
21
+ """Encapsulates a model and data for convenient access to kinematic quantities.
22
+
23
+ This class provides methods to access and update the kinematic quantities of a robot
24
+ model, such as frame transforms and Jacobians. It performs forward kinematics at every
25
+ time step, ensuring up-to-date information about the robot's state.
14
26
 
15
- The `update` function ensures the proper forward kinematics functions have been
16
- called, namely:
27
+ Key functionalities include:
17
28
 
18
- - mujoco.mj_kinematics(model, data)
19
- - mujoco.mj_comPos(model, data)
29
+ * Running forward kinematics to update the state.
30
+ * Checking configuration limits.
31
+ * Computing Jacobians for different frames.
32
+ * Retrieving frame transforms relative to the world frame.
33
+ * Integrating velocities to update configurations.
20
34
  """
21
35
 
22
36
  def __init__(
@@ -27,9 +41,9 @@ class Configuration:
27
41
  """Constructor.
28
42
 
29
43
  Args:
30
- model: An instance of MjModel.
31
- q: Optional configuration to initialize from. If None, the configuration
32
- is initialized to the reference configuration `qpos0`.
44
+ model: Mujoco model.
45
+ q: Configuration to initialize from. If None, the configuration
46
+ is initialized to the default configuration `qpos0`.
33
47
  """
34
48
  self.model = model
35
49
  self.data = mujoco.MjData(model)
@@ -38,15 +52,13 @@ class Configuration:
38
52
  def update(self, q: Optional[np.ndarray] = None) -> None:
39
53
  """Run forward kinematics.
40
54
 
41
- The minimal function call required to get updated frame transforms (aka forward
42
- kinematics) is `mj_kinematics`. An extra call to `mj_comPos` is needed for
43
- updated Jacobians.
44
-
45
- Args:`
46
- q: Optional configuration vector to override internal data.qpos with.
55
+ Args:
56
+ q: Optional configuration vector to override internal `data.qpos` with.
47
57
  """
48
58
  if q is not None:
49
59
  self.data.qpos = q
60
+ # The minimal function call required to get updated frame transforms is
61
+ # mj_kinematics. An extra call to mj_comPos is required for updated Jacobians.
50
62
  mujoco.mj_kinematics(self.model, self.data)
51
63
  mujoco.mj_comPos(self.model, self.data)
52
64
 
@@ -55,9 +67,6 @@ class Configuration:
55
67
 
56
68
  Args:
57
69
  key_name: The name of the keyframe.
58
-
59
- Raises:
60
- ValueError: if no key named `key` was found in the model.
61
70
  """
62
71
  key_id = mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_KEY, key_name)
63
72
  if key_id == -1:
@@ -95,24 +104,27 @@ class Configuration:
95
104
  )
96
105
  else:
97
106
  print(
98
- f"Value {qval} at index {jnt} is out of limits: "
99
- f"[{qmin}, {qmax}]"
107
+ f"Value {qval:.2f} at index {jnt} is outside of its limits: "
108
+ f"[{qmin:.2f}, {qmax:.2f}]"
100
109
  )
101
110
 
102
111
  def get_frame_jacobian(self, frame_name: str, frame_type: str) -> np.ndarray:
103
- """Compute the Jacobian matrix of a frame velocity.
112
+ r"""Compute the Jacobian matrix of a frame velocity.
113
+
114
+ Denoting our frame by :math:`B` and the world frame by :math:`W`, the
115
+ Jacobian matrix :math:`{}_B J_{WB}` is related to the body velocity
116
+ :math:`{}_B v_{WB}` by:
104
117
 
105
- Denoting our frame by B and the world frame by W, the Jacobian matrix B_J_WB
106
- is related to the body velocity B_v_WB by:
118
+ .. math::
107
119
 
108
- B_v_WB = B_J_WB q_dot
120
+ {}_B v_{WB} = {}_B J_{WB} \dot{q}
109
121
 
110
122
  Args:
111
123
  frame_name: Name of the frame in the MJCF.
112
124
  frame_type: Type of frame. Can be a geom, a body or a site.
113
125
 
114
126
  Returns:
115
- Jacobian B_J_WB of the frame.
127
+ Jacobian :math:`{}_B J_{WB}` of the frame.
116
128
  """
117
129
  if frame_type not in consts.SUPPORTED_FRAMES:
118
130
  raise exceptions.UnsupportedFrame(frame_type, consts.SUPPORTED_FRAMES)
@@ -142,9 +154,7 @@ class Configuration:
142
154
  return jac
143
155
 
144
156
  def get_transform_frame_to_world(self, frame_name: str, frame_type: str) -> SE3:
145
- """Get the pose of a frame in the current configuration.
146
-
147
- Denoting our frame by B and the world frame by W, this function returns T_WB.
157
+ """Get the pose of a frame at the current configuration.
148
158
 
149
159
  Args:
150
160
  frame_name: Name of the frame in the MJCF.
@@ -173,11 +183,38 @@ class Configuration:
173
183
  translation=xpos,
174
184
  )
175
185
 
186
+ def get_transform(
187
+ self,
188
+ source_name: str,
189
+ source_type: str,
190
+ dest_name: str,
191
+ dest_type: str,
192
+ ) -> SE3:
193
+ """Get the pose of a frame with respect to another frame at the current
194
+ configuration.
195
+
196
+ Args:
197
+ source_name: Name of the frame in the MJCF.
198
+ source_type: Source type of frame. Can be a geom, a body or a site.
199
+ dest_name: Name of the frame to get the pose in.
200
+ dest_type: Dest type of frame. Can be a geom, a body or a site.
201
+
202
+ Returns:
203
+ The pose of `source_name` in `dest_name`.
204
+ """
205
+ transform_source_to_world = self.get_transform_frame_to_world(
206
+ source_name, source_type
207
+ )
208
+ transform_dest_to_world = self.get_transform_frame_to_world(
209
+ dest_name, dest_type
210
+ )
211
+ return transform_dest_to_world.inverse() @ transform_source_to_world
212
+
176
213
  def integrate(self, velocity: np.ndarray, dt: float) -> np.ndarray:
177
214
  """Integrate a velocity starting from the current configuration.
178
215
 
179
216
  Args:
180
- velocity: The velocity.
217
+ velocity: The velocity in tangent space.
181
218
  dt: Integration duration in [s].
182
219
 
183
220
  Returns:
@@ -191,7 +228,7 @@ class Configuration:
191
228
  """Integrate a velocity and update the current configuration inplace.
192
229
 
193
230
  Args:
194
- velocity: The velocity.
231
+ velocity: The velocity in tangent space.
195
232
  dt: Integration duration in [s].
196
233
  """
197
234
  mujoco.mj_integratePos(self.model, self.data.qpos, velocity, dt)
mink/lie/__init__.py CHANGED
@@ -1,13 +1,12 @@
1
1
  from .base import MatrixLieGroup
2
2
  from .se3 import SE3
3
3
  from .so3 import SO3
4
- from .utils import get_epsilon, mat2quat, skew
4
+ from .utils import get_epsilon, skew
5
5
 
6
6
  __all__ = (
7
7
  "SE3",
8
8
  "SO3",
9
9
  "MatrixLieGroup",
10
10
  "get_epsilon",
11
- "mat2quat",
12
11
  "skew",
13
12
  )
mink/lie/base.py CHANGED
@@ -10,8 +10,8 @@ class MatrixLieGroup(abc.ABC):
10
10
 
11
11
  Attributes:
12
12
  matrix_dim: Dimension of square matrix output.
13
- parameters_dim:: Dimension of underlying parameters.
14
- tangent_dim:: Dimension of tangent space.
13
+ parameters_dim: Dimension of underlying parameters.
14
+ tangent_dim: Dimension of tangent space.
15
15
  space_dim: Dimension of coordinates that can be transformed.
16
16
  """
17
17
 
mink/lie/so3.py CHANGED
@@ -14,6 +14,8 @@ _INVERT_QUAT_SIGN = np.array([1.0, -1.0, -1.0, -1.0], dtype=np.float64)
14
14
 
15
15
 
16
16
  class RollPitchYaw(NamedTuple):
17
+ """Struct containing roll, pitch, and yaw Euler angles."""
18
+
17
19
  roll: float
18
20
  pitch: float
19
21
  yaw: float
mink/lie/utils.py CHANGED
@@ -1,4 +1,3 @@
1
- import mujoco
2
1
  import numpy as np
3
2
 
4
3
 
@@ -17,13 +16,6 @@ def skew(x: np.ndarray) -> np.ndarray:
17
16
  [0.0, -wz, wy],
18
17
  [wz, 0.0, -wx],
19
18
  [-wy, wx, 0.0],
20
- ]
19
+ ],
20
+ dtype=x.dtype,
21
21
  )
22
-
23
-
24
- def mat2quat(mat: np.ndarray):
25
- """Convert a MuJoCo matrix (9,) to a quaternion (4,)."""
26
- assert mat.shape == (9,)
27
- quat = np.empty(4, dtype=np.float64)
28
- mujoco.mju_mat2Quat(quat, mat)
29
- return quat
@@ -1,32 +1,4 @@
1
- """Collision avoidance limit.
2
-
3
- Derivation
4
- ==========
5
-
6
- p1, p2: closest points between g1 and g2
7
- d: distance between g1 and g2 (d = ||p1 - p2||)
8
- n: normal vector from g1 to g2 (n = (p2 - p1) / ||p2 - p1||)
9
-
10
- The relative velocity constraint between g1 and g2 is given by:
11
-
12
- n^T [J2(p2) - J1(p1)] dq <= -k * (d - d_min) / dt
13
- V_n <= -k * (d - d_min)
14
-
15
- where [J2(p2) - J1(p1)] dq denotes the relative velocity between g1 and g2, and
16
- n^T [J2(p2) - J1(p1)] dq projects this velocity onto the direction connecting the
17
- closest points p1 and p2.
18
-
19
- We have three cases for the distance d:
20
-
21
- 1. V_n <= R_b if dist_c < dist_m
22
- 2. V_n <= -k * (dist_c - dist_m) / dt + R_b if dist_m <= d <= dist_d
23
- 3. V_n <= inf otherwise
24
-
25
- where:
26
- - dist_c: current normal distance between g1 and g2
27
- - dist_m: minimum allowed distance between g1 and g2
28
- - dist_d: collision detection distance
29
- """
1
+ """Collision avoidance limit."""
30
2
 
31
3
  import itertools
32
4
  from dataclasses import dataclass
@@ -63,6 +35,22 @@ class Contact:
63
35
  return self.dist == self.distmax and not self.fromto.any()
64
36
 
65
37
 
38
+ def compute_contact_normal_jacobian(
39
+ model: mujoco.MjModel,
40
+ data: mujoco.MjData,
41
+ contact: Contact,
42
+ ) -> np.ndarray:
43
+ geom1_body = model.geom_bodyid[contact.geom1]
44
+ geom2_body = model.geom_bodyid[contact.geom2]
45
+ geom1_contact_pos = contact.fromto[:3]
46
+ geom2_contact_pos = contact.fromto[3:]
47
+ jac2 = np.empty((3, model.nv))
48
+ mujoco.mj_jac(model, data, jac2, None, geom2_contact_pos, geom2_body)
49
+ jac1 = np.empty((3, model.nv))
50
+ mujoco.mj_jac(model, data, jac1, None, geom1_contact_pos, geom1_body)
51
+ return contact.normal @ (jac2 - jac1)
52
+
53
+
66
54
  def _is_welded_together(model: mujoco.MjModel, geom_id1: int, geom_id2: int) -> bool:
67
55
  """Returns true if the geoms are part of the same body, or if their bodies are
68
56
  welded together."""
@@ -107,7 +95,31 @@ def _is_pass_contype_conaffinity_check(
107
95
 
108
96
 
109
97
  class CollisionAvoidanceLimit(Limit):
110
- """Normal velocity limit between geom pairs."""
98
+ """Normal velocity limit between geom pairs.
99
+
100
+ Attributes:
101
+ model: MuJoCo model.
102
+ geom_pairs: Set of collision pairs in which to perform active collision
103
+ avoidance. A collision pair is defined as a pair of geom groups. A geom
104
+ group is a set of geom names. For each geom pair, the solver will
105
+ attempt to compute joint velocities that avoid collisions between every
106
+ geom in the first geom group with every geom in the second geom group.
107
+ Self collision is achieved by adding a collision pair with the same
108
+ geom group in both pair fields.
109
+ gain: Gain factor in (0, 1] that determines how fast the geoms are
110
+ allowed to move towards each other at each iteration. Smaller values
111
+ are safer but may make the geoms move slower towards each other.
112
+ minimum_distance_from_collisions: The minimum distance to leave between
113
+ any two geoms. A negative distance allows the geoms to penetrate by
114
+ the specified amount.
115
+ collision_detection_distance: The distance between two geoms at which the
116
+ active collision avoidance limit will be active. A large value will
117
+ cause collisions to be detected early, but may incur high computational
118
+ cost. A negative value will cause the geoms to be detected only after
119
+ they penetrate by the specified amount.
120
+ bound_relaxation: An offset on the upper bound of each collision avoidance
121
+ constraint.
122
+ """
111
123
 
112
124
  def __init__(
113
125
  self,
@@ -141,7 +153,7 @@ class CollisionAvoidanceLimit(Limit):
141
153
  cost. A negative value will cause the geoms to be detected only after
142
154
  they penetrate by the specified amount.
143
155
  bound_relaxation: An offset on the upper bound of each collision avoidance
144
- constraint to tighten or relax
156
+ constraint.
145
157
  """
146
158
  self.model = model
147
159
  self.gain = gain
@@ -170,7 +182,9 @@ class CollisionAvoidanceLimit(Limit):
170
182
  upper_bound[idx] = (self.gain * dist / dt) + self.bound_relaxation
171
183
  else:
172
184
  upper_bound[idx] = self.bound_relaxation
173
- jac = self._compute_contact_normal_jacobian(configuration.data, contact)
185
+ jac = compute_contact_normal_jacobian(
186
+ self.model, configuration.data, contact
187
+ )
174
188
  coefficient_matrix[idx] = -jac
175
189
  return Constraint(G=coefficient_matrix, h=upper_bound)
176
190
 
@@ -193,37 +207,6 @@ class CollisionAvoidanceLimit(Limit):
193
207
  dist, fromto, geom1_id, geom2_id, self.collision_detection_distance
194
208
  )
195
209
 
196
- def _compute_contact_normal_jacobian(
197
- self, data: mujoco.MjData, contact: Contact
198
- ) -> np.ndarray:
199
- """Computes the Jacobian mapping joint velocities to the normal component of
200
- the relative Cartesian linear velocity between the geom pair.
201
-
202
- The Jacobian-velocity relationship is given as:
203
-
204
- J dq = n^T (v_2 - v_1)
205
-
206
- where:
207
- * J is the computed Jacobian.
208
- * dq is the joint velocity vector.
209
- * n^T is the transpose of the normal pointing from contact.geom1 to
210
- contact.geom2.
211
- * v_1, v_2 are the linear components of the Cartesian velocity of the two
212
- closest points in contact.geom1 and contact.geom2.
213
-
214
- Note: n^T (v_2 - v_1) is a scalar that is positive if the geoms are moving away
215
- from each other, and negative if they are moving towards each other.
216
- """
217
- geom1_body = self.model.geom_bodyid[contact.geom1]
218
- geom2_body = self.model.geom_bodyid[contact.geom2]
219
- geom1_contact_pos = contact.fromto[:3]
220
- geom2_contact_pos = contact.fromto[3:]
221
- jac2 = np.empty((3, self.model.nv))
222
- mujoco.mj_jac(self.model, data, jac2, None, geom2_contact_pos, geom2_body)
223
- jac1 = np.empty((3, self.model.nv))
224
- mujoco.mj_jac(self.model, data, jac1, None, geom1_contact_pos, geom1_body)
225
- return contact.normal @ (jac2 - jac1)
226
-
227
210
  def _homogenize_geom_id_list(self, geom_list: GeomSequence) -> List[int]:
228
211
  """Take a heterogeneous list of geoms (specified via ID or name) and return
229
212
  a homogenous list of IDs (int)."""
@@ -1,24 +1,4 @@
1
- """Limit on joint positions.
2
-
3
- Derivation
4
- ==========
5
-
6
- Using a first order Taylor expansion on the configuration, we can write the limit as:
7
-
8
- q_min <= q + v * dt <= q_max
9
- q_min <= q + dq <= q_max
10
- q_min - q <= dq <= q_max - q
11
-
12
- Rewriting as G dq <= h:
13
-
14
- +I * dq <= q_max - q
15
- -I * dq <= q - q_min
16
-
17
- Stacking them together, we get:
18
-
19
- G = [+I, -I]
20
- h = [q_max - q, q - q_min]
21
- """
1
+ """Joint position limit."""
22
2
 
23
3
  import mujoco
24
4
  import numpy as np
@@ -30,16 +10,9 @@ from .limit import Constraint, Limit
30
10
 
31
11
 
32
12
  class ConfigurationLimit(Limit):
33
- """Limit for joint positions in a model.
34
-
35
- Floating base joints (joint type="free") are ignored.
13
+ """Inequality constraint on joint positions in a robot model.
36
14
 
37
- Attributes:
38
- indices: Tangent indices corresponding to configuration-limited joints.
39
- projection_matrix: Projection from tangent space to subspace with
40
- configuration-limited joints.
41
- lower: Lower configuration limit.
42
- upper: Upper configuration limit.
15
+ Floating base joints are ignored.
43
16
  """
44
17
 
45
18
  def __init__(
@@ -95,6 +68,26 @@ class ConfigurationLimit(Limit):
95
68
  configuration: Configuration,
96
69
  dt: float,
97
70
  ) -> Constraint:
71
+ r"""Compute the configuration-dependent joint position limits.
72
+
73
+ The limits are defined as:
74
+
75
+ .. math::
76
+
77
+ {q \ominus q_{min}} \leq \Delta q \leq {q_{max} \ominus q}
78
+
79
+ where :math:`q \in {\cal C}` is the robot's configuration and
80
+ :math:`\Delta q \in T_q({\cal C})` is the displacement in the tangent
81
+ space at :math:`q`. See the :ref:`derivations` section for more information.
82
+
83
+ Args:
84
+ configuration: Robot configuration :math:`q`.
85
+ dt: Integration timestep in [s].
86
+
87
+ Returns:
88
+ Pair :math:`(G, h)` representing the inequality constraint as
89
+ :math:`G \Delta q \leq h`, or ``None`` if there is no limit.
90
+ """
98
91
  del dt # Unused.
99
92
 
100
93
  # Upper.
mink/limits/limit.py CHANGED
@@ -1,3 +1,5 @@
1
+ """All kinematic limits derive from the :class:`Limit` base class."""
2
+
1
3
  import abc
2
4
  from typing import NamedTuple, Optional
3
5
 
@@ -7,21 +9,29 @@ from ..configuration import Configuration
7
9
 
8
10
 
9
11
  class Constraint(NamedTuple):
10
- """Linear inequalities in the form G(q) dq <= h(q).
12
+ r"""Linear inequality constraint of the form :math:`G(q) \Delta q \leq h(q)`.
11
13
 
12
- The limit is considered inactive when G or h are None.
14
+ Inactive if G and h are None.
13
15
  """
14
16
 
15
- G: Optional[np.ndarray] = None # (nv, nv)
16
- h: Optional[np.ndarray] = None # (nv,)
17
+ G: Optional[np.ndarray] = None
18
+ """Shape (nv, nv)."""
19
+ h: Optional[np.ndarray] = None
20
+ """Shape (nv,)."""
17
21
 
18
22
  @property
19
23
  def inactive(self) -> bool:
20
- return self.G is None or self.h is None
24
+ """Returns True if the constraint is inactive."""
25
+ return self.G is None and self.h is None
21
26
 
22
27
 
23
28
  class Limit(abc.ABC):
24
- """Abstract base class for kinematic limits."""
29
+ """Abstract base class for kinematic limits.
30
+
31
+ Subclasses must implement the :py:meth:`~Limit.compute_qp_inequalities` method
32
+ which takes in the current robot configuration and integration time step and
33
+ returns an instance of :class:`Constraint`.
34
+ """
25
35
 
26
36
  @abc.abstractmethod
27
37
  def compute_qp_inequalities(
@@ -29,13 +39,21 @@ class Limit(abc.ABC):
29
39
  configuration: Configuration,
30
40
  dt: float,
31
41
  ) -> Constraint:
32
- """Compute limit as linearized QP inequalities.
42
+ r"""Compute limit as linearized QP inequalities of the form:
43
+
44
+ .. math::
45
+
46
+ G(q) \Delta q \leq h(q)
47
+
48
+ where :math:`q \in {\cal C}` is the robot's configuration and
49
+ :math:`\Delta q \in T_q({\cal C})` is the displacement in the tangent
50
+ space at :math:`q`.
33
51
 
34
52
  Args:
35
- configuration: Current configuration.
53
+ configuration: Robot configuration :math:`q`.
36
54
  dt: Integration time step in [s].
37
55
 
38
56
  Returns:
39
- Pair (G, h) representing the inequality constraint.
57
+ Pair :math:`(G, h)`.
40
58
  """
41
59
  raise NotImplementedError
@@ -1,24 +1,4 @@
1
- """Limit on joint velocities.
2
-
3
- Derivation
4
- ==========
5
-
6
- Given maximum joint velocity magnitudes v_max, we can express joint velocity limits as:
7
-
8
- -v_max <= v <= v_max
9
- -v_max <= dq / dt <= v_max
10
- -v_max * dt <= dq <= v_max * dt
11
-
12
- Rewriting as G dq <= h:
13
-
14
- +I * dq <= v_max * dt
15
- -I * dq <= v_max * dt
16
-
17
- Stacking them together, we get:
18
-
19
- G = [+I, -I]
20
- h = [v_max * dt, v_max * dt]
21
- """
1
+ """Joint velocity limit."""
22
2
 
23
3
  from typing import Mapping
24
4
 
@@ -33,17 +13,22 @@ from .limit import Constraint, Limit
33
13
 
34
14
 
35
15
  class VelocityLimit(Limit):
36
- """Limit for joint velocities in a model.
16
+ """Inequality constraint on joint velocities in a robot model.
37
17
 
38
18
  Floating base joints are ignored.
39
19
 
40
20
  Attributes:
41
21
  indices: Tangent indices corresponding to velocity-limited joints.
42
- limit: Maximum allowed velocity magnitude for velocity-limited joints.
22
+ limit: Maximum allowed velocity magnitude for velocity-limited joints, in
23
+ [m]/[s] for slide joints and [rad]/[s] for hinge joints.
43
24
  projection_matrix: Projection from tangent space to subspace with
44
25
  velocity-limited joints.
45
26
  """
46
27
 
28
+ indices: np.ndarray
29
+ limit: np.ndarray
30
+ projection_matrix: np.ndarray
31
+
47
32
  def __init__(
48
33
  self,
49
34
  model: mujoco.MjModel,
@@ -52,6 +37,7 @@ class VelocityLimit(Limit):
52
37
  """Initialize velocity limits.
53
38
 
54
39
  Args:
40
+ model: MuJoCo model.
55
41
  velocities: Dictionary mapping joint name to maximum allowed magnitude in
56
42
  [m]/[s] for slide joints and [rad]/[s] for hinge joints.
57
43
  """
@@ -84,6 +70,27 @@ class VelocityLimit(Limit):
84
70
  def compute_qp_inequalities(
85
71
  self, configuration: Configuration, dt: float
86
72
  ) -> Constraint:
73
+ r"""Compute the configuration-dependent joint velocity limits.
74
+
75
+ The limits are defined as:
76
+
77
+ .. math::
78
+
79
+ -v_{\text{max}} \cdot dt \leq \Delta q \leq v_{\text{max}} \cdot dt
80
+
81
+ where :math:`v_{max} \in {\cal T}` is the robot's velocity limit
82
+ vector and :math:`\Delta q \in T_q({\cal C})` is the displacement in the
83
+ tangent space at :math:`q`. See the :ref:`derivations` section for
84
+ more information.
85
+
86
+ Args:
87
+ configuration: Robot configuration :math:`q`.
88
+ dt: Integration timestep in [s].
89
+
90
+ Returns:
91
+ Pair :math:`(G, h)` representing the inequality constraint as
92
+ :math:`G \Delta q \leq h`, or ``None`` if there is no limit.
93
+ """
87
94
  del configuration # Unused.
88
95
  if self.projection_matrix is None:
89
96
  return Constraint()
mink/solve_ik.py CHANGED
@@ -27,8 +27,8 @@ def _compute_qp_inequalities(
27
27
  ) -> tuple[Optional[np.ndarray], Optional[np.ndarray]]:
28
28
  if limits is None:
29
29
  limits = [ConfigurationLimit(configuration.model)]
30
- G_list = []
31
- h_list = []
30
+ G_list: list[np.ndarray] = []
31
+ h_list: list[np.ndarray] = []
32
32
  for limit in limits:
33
33
  inequality = limit.compute_qp_inequalities(configuration, dt)
34
34
  if not inequality.inactive:
mink/tasks/__init__.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Kinematic tasks."""
2
2
 
3
3
  from .com_task import ComTask
4
+ from .damping_task import DampingTask
4
5
  from .exceptions import (
5
6
  InvalidDamping,
6
7
  InvalidGain,
@@ -10,13 +11,16 @@ from .exceptions import (
10
11
  )
11
12
  from .frame_task import FrameTask
12
13
  from .posture_task import PostureTask
14
+ from .relative_frame_task import RelativeFrameTask
13
15
  from .task import Objective, Task
14
16
 
15
17
  __all__ = (
16
18
  "ComTask",
17
19
  "FrameTask",
18
20
  "Objective",
21
+ "DampingTask",
19
22
  "PostureTask",
23
+ "RelativeFrameTask",
20
24
  "Task",
21
25
  "TargetNotSet",
22
26
  "InvalidTarget",