mink 0.0.10__py3-none-any.whl → 0.0.12__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
@@ -9,11 +9,20 @@ from .constants import (
9
9
  SUPPORTED_FRAMES,
10
10
  )
11
11
  from .exceptions import (
12
+ IntegrationTimestepNotSet,
13
+ InvalidConstraint,
14
+ InvalidDamping,
12
15
  InvalidFrame,
16
+ InvalidGain,
13
17
  InvalidKeyframe,
14
18
  InvalidMocapBody,
19
+ InvalidTarget,
20
+ LimitDefinitionError,
15
21
  MinkError,
22
+ NoSolutionFound,
16
23
  NotWithinConfigurationLimits,
24
+ TargetNotSet,
25
+ TaskDefinitionError,
17
26
  UnsupportedFrame,
18
27
  )
19
28
  from .lie import SE3, SO3, MatrixLieGroup
@@ -30,10 +39,10 @@ from .tasks import (
30
39
  DampingTask,
31
40
  EqualityConstraintTask,
32
41
  FrameTask,
42
+ KineticEnergyRegularizationTask,
33
43
  Objective,
34
44
  PostureTask,
35
45
  RelativeFrameTask,
36
- TargetNotSet,
37
46
  Task,
38
47
  )
39
48
  from .utils import (
@@ -55,6 +64,7 @@ __all__ = (
55
64
  "RelativeFrameTask",
56
65
  "PostureTask",
57
66
  "Task",
67
+ "KineticEnergyRegularizationTask",
58
68
  "EqualityConstraintTask",
59
69
  "Objective",
60
70
  "ConfigurationLimit",
@@ -72,6 +82,14 @@ __all__ = (
72
82
  "NotWithinConfigurationLimits",
73
83
  "TargetNotSet",
74
84
  "InvalidMocapBody",
85
+ "InvalidConstraint",
86
+ "InvalidDamping",
87
+ "InvalidGain",
88
+ "InvalidTarget",
89
+ "LimitDefinitionError",
90
+ "TaskDefinitionError",
91
+ "IntegrationTimestepNotSet",
92
+ "NoSolutionFound",
75
93
  "SUPPORTED_FRAMES",
76
94
  "FRAME_TO_ENUM",
77
95
  "FRAME_TO_JAC_FUNC",
mink/configuration.py CHANGED
@@ -30,6 +30,7 @@ class Configuration:
30
30
  * Running forward kinematics to update the state.
31
31
  * Checking configuration limits.
32
32
  * Computing Jacobians for different frames.
33
+ * Computing the joint-space inertia matrix.
33
34
  * Retrieving frame transforms relative to the world frame.
34
35
  * Integrating velocities to update configurations.
35
36
  """
@@ -84,6 +85,10 @@ class Configuration:
84
85
  safety_break: If True, stop execution and raise an exception if the current
85
86
  configuration is outside limits. If False, print a warning and continue
86
87
  execution.
88
+
89
+ Raises:
90
+ NotWithinConfigurationLimits: If the current configuration is outside
91
+ the joint limits.
87
92
  """
88
93
  for jnt in range(self.model.njnt):
89
94
  jnt_type = self.model.jnt_type[jnt]
@@ -237,6 +242,24 @@ class Configuration:
237
242
  mujoco.mj_integratePos(self.model, self.data.qpos, velocity, dt)
238
243
  self.update()
239
244
 
245
+ def get_inertia_matrix(self) -> np.ndarray:
246
+ r"""Return the joint-space inertia matrix at the current configuration.
247
+
248
+ Returns:
249
+ The joint-space inertia matrix :math:`M(\mathbf{q})`.
250
+ """
251
+ # Run the composite rigid body inertia (CRB) algorithm to populate the joint
252
+ # space inertia matrix data.qM.
253
+ if mujoco.mj_version() >= 334:
254
+ mujoco.mj_makeM(self.model, self.data)
255
+ else:
256
+ mujoco.mj_crb(self.model, self.data)
257
+ # data.qM is stored in a custom sparse format and can be converted to dense
258
+ # format using mujoco.mj_fullM.
259
+ M = np.empty((self.nv, self.nv), dtype=np.float64)
260
+ mujoco.mj_fullM(self.model, M, self.data.qM)
261
+ return M
262
+
240
263
  # Aliases.
241
264
 
242
265
  @property
mink/exceptions.py CHANGED
@@ -9,6 +9,13 @@ class MinkError(Exception):
9
9
  """Base class for Mink exceptions."""
10
10
 
11
11
 
12
+ class NoSolutionFound(MinkError):
13
+ """Exception raised when the QP solver fails to find a solution."""
14
+
15
+ def __init__(self, solver_name: str):
16
+ super().__init__(f"QP solver {solver_name} failed to find a solution.")
17
+
18
+
12
19
  class UnsupportedFrame(MinkError):
13
20
  """Exception raised when a frame type is unsupported."""
14
21
 
@@ -96,3 +103,43 @@ class NotWithinConfigurationLimits(MinkError):
96
103
  f"{lower} <= {value} <= {upper}"
97
104
  )
98
105
  super().__init__(message)
106
+
107
+
108
+ class TaskDefinitionError(MinkError):
109
+ """Exception raised when a task definition is ill-formed."""
110
+
111
+
112
+ class IntegrationTimestepNotSet(MinkError):
113
+ """Exception raised when the integration timestep is not set."""
114
+
115
+ def __init__(self, cls_name: str):
116
+ message = f"No integration timestep set for {cls_name}"
117
+ super().__init__(message)
118
+
119
+
120
+ class TargetNotSet(MinkError):
121
+ """Exception raised when attempting to use a task with an unset target."""
122
+
123
+ def __init__(self, cls_name: str):
124
+ message = f"No target set for {cls_name}"
125
+ super().__init__(message)
126
+
127
+
128
+ class InvalidTarget(MinkError):
129
+ """Exception raised when the target is invalid."""
130
+
131
+
132
+ class InvalidGain(MinkError):
133
+ """Exception raised when the gain is outside the valid range."""
134
+
135
+
136
+ class InvalidDamping(MinkError):
137
+ """Exception raised when the damping is outside the valid range."""
138
+
139
+
140
+ class InvalidConstraint(MinkError):
141
+ """Exception raised when a constraint is invalid."""
142
+
143
+
144
+ class LimitDefinitionError(MinkError):
145
+ """Exception raised when a limit definition is ill-formed."""
mink/lie/se3.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
+ from typing import Tuple
4
5
 
5
6
  import mujoco
6
7
  import numpy as np
@@ -221,6 +222,36 @@ class SE3(MatrixLieGroup):
221
222
  ljacinv_se3[3:, 3:] = ljacinv_so3
222
223
  return ljacinv_se3
223
224
 
225
+ def clamp(
226
+ self,
227
+ x_translation: Tuple[float, float] = (-np.inf, np.inf),
228
+ y_translation: Tuple[float, float] = (-np.inf, np.inf),
229
+ z_translation: Tuple[float, float] = (-np.inf, np.inf),
230
+ roll_radians: Tuple[float, float] = (-np.inf, np.inf),
231
+ pitch_radians: Tuple[float, float] = (-np.inf, np.inf),
232
+ yaw_radians: Tuple[float, float] = (-np.inf, np.inf),
233
+ ) -> SE3:
234
+ """Clamp a SE3 within translation and RPY limits.
235
+
236
+ Args:
237
+ x_translation: The (lower, upper) limits for translation along the x axis.
238
+ y_translation: The (lower, upper) limits for translation along the y axis.
239
+ z_translation: The (lower, upper) limits for translation along the z axis.
240
+ roll_radians: The (lower, upper) limits for the roll.
241
+ pitch_radians: The (lower, upper) limits for the pitch.
242
+ yaw_radians: The (lower, upper) limits for the yaw.
243
+
244
+ Returns:
245
+ A SE3 within the translation and RPY limits.
246
+ """
247
+ translation_limits = np.array([x_translation, y_translation, z_translation])
248
+ return SE3.from_rotation_and_translation(
249
+ rotation=self.rotation().clamp(roll_radians, pitch_radians, yaw_radians),
250
+ translation=np.clip(
251
+ self.translation(), translation_limits[:, 0], translation_limits[:, 1]
252
+ ),
253
+ )
254
+
224
255
 
225
256
  # Eqn 180.
226
257
  def _getQ(c) -> np.ndarray:
mink/lie/so3.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import NamedTuple
4
+ from typing import NamedTuple, Tuple
5
5
 
6
6
  import mujoco
7
7
  import numpy as np
@@ -260,3 +260,25 @@ class SO3(MatrixLieGroup):
260
260
  ljacinv[1, 1] += 1.0
261
261
  ljacinv[2, 2] += 1.0
262
262
  return ljacinv
263
+
264
+ def clamp(
265
+ self,
266
+ roll_radians: Tuple[float, float] = (-np.inf, np.inf),
267
+ pitch_radians: Tuple[float, float] = (-np.inf, np.inf),
268
+ yaw_radians: Tuple[float, float] = (-np.inf, np.inf),
269
+ ) -> SO3:
270
+ """Clamp a SO3 within RPY limits.
271
+
272
+ Args:
273
+ roll_radians: The (lower, upper) limits for the roll.
274
+ pitch_radians: The (lower, upper) limits for the pitch.
275
+ yaw_radians: The (lower, upper) limits for the yaw.
276
+
277
+ Returns:
278
+ A SO3 within the RPY limits.
279
+ """
280
+ return SO3.from_rpy_radians(
281
+ roll=np.clip(self.compute_roll_radians(), *roll_radians),
282
+ pitch=np.clip(self.compute_pitch_radians(), *pitch_radians),
283
+ yaw=np.clip(self.compute_yaw_radians(), *yaw_radians),
284
+ )
mink/limits/__init__.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  from .collision_avoidance_limit import CollisionAvoidanceLimit
4
4
  from .configuration_limit import ConfigurationLimit
5
- from .exceptions import LimitDefinitionError
6
5
  from .limit import Constraint, Limit
7
6
  from .velocity_limit import VelocityLimit
8
7
 
@@ -12,5 +11,4 @@ __all__ = (
12
11
  "Constraint",
13
12
  "Limit",
14
13
  "VelocityLimit",
15
- "LimitDefinitionError",
16
14
  )
@@ -7,7 +7,7 @@ import numpy as np
7
7
 
8
8
  from ..configuration import Configuration
9
9
  from ..constants import dof_width, qpos_width
10
- from .exceptions import LimitDefinitionError
10
+ from ..exceptions import LimitDefinitionError
11
11
  from .limit import Constraint, Limit
12
12
 
13
13
 
@@ -98,7 +98,7 @@ class ConfigurationLimit(Limit):
98
98
  return Constraint()
99
99
 
100
100
  # Upper.
101
- delta_q_max = np.zeros((self.model.nv,))
101
+ delta_q_max = np.empty((self.model.nv,))
102
102
  mujoco.mj_differentiatePos(
103
103
  m=self.model,
104
104
  qvel=delta_q_max,
@@ -108,7 +108,7 @@ class ConfigurationLimit(Limit):
108
108
  )
109
109
 
110
110
  # Lower.
111
- delta_q_min = np.zeros((self.model.nv,))
111
+ delta_q_min = np.empty((self.model.nv,))
112
112
  mujoco.mj_differentiatePos(
113
113
  m=self.model,
114
114
  qvel=delta_q_min,
@@ -8,7 +8,7 @@ import numpy.typing as npt
8
8
 
9
9
  from ..configuration import Configuration
10
10
  from ..constants import dof_width
11
- from .exceptions import LimitDefinitionError
11
+ from ..exceptions import LimitDefinitionError
12
12
  from .limit import Constraint, Limit
13
13
 
14
14
 
mink/solve_ik.py CHANGED
@@ -6,12 +6,13 @@ import numpy as np
6
6
  import qpsolvers
7
7
 
8
8
  from .configuration import Configuration
9
+ from .exceptions import NoSolutionFound
9
10
  from .limits import ConfigurationLimit, Limit
10
- from .tasks import Objective, Task
11
+ from .tasks import BaseTask, Objective
11
12
 
12
13
 
13
14
  def _compute_qp_objective(
14
- configuration: Configuration, tasks: Sequence[Task], damping: float
15
+ configuration: Configuration, tasks: Sequence[BaseTask], damping: float
15
16
  ) -> Objective:
16
17
  H = np.eye(configuration.model.nv) * damping
17
18
  c = np.zeros(configuration.model.nv)
@@ -42,18 +43,31 @@ def _compute_qp_inequalities(
42
43
 
43
44
  def build_ik(
44
45
  configuration: Configuration,
45
- tasks: Sequence[Task],
46
+ tasks: Sequence[BaseTask],
46
47
  dt: float,
47
48
  damping: float = 1e-12,
48
49
  limits: Optional[Sequence[Limit]] = None,
49
50
  ) -> qpsolvers.Problem:
50
- """Build quadratic program from current configuration and tasks.
51
+ r"""Build the quadratic program given the current configuration and tasks.
52
+
53
+ The quadratic program is defined as:
54
+
55
+ .. math::
56
+
57
+ \begin{align*}
58
+ \min_{\Delta q} & \frac{1}{2} \Delta q^T H \Delta q + c^T \Delta q \\
59
+ \text{s.t.} \quad & G \Delta q \leq h
60
+ \end{align*}
61
+
62
+ where :math:`\Delta q = v / dt` is the vector of joint displacements.
51
63
 
52
64
  Args:
53
65
  configuration: Robot configuration.
54
66
  tasks: List of kinematic tasks.
55
67
  dt: Integration timestep in [s].
56
- damping: Levenberg-Marquardt damping.
68
+ damping: Levenberg-Marquardt damping. Higher values improve numerical
69
+ stability but slow down task convergence. This value applies to all
70
+ dofs, including floating-base coordinates.
57
71
  limits: List of limits to enforce. Set to empty list to disable. If None,
58
72
  defaults to a configuration limit.
59
73
 
@@ -67,7 +81,7 @@ def build_ik(
67
81
 
68
82
  def solve_ik(
69
83
  configuration: Configuration,
70
- tasks: Sequence[Task],
84
+ tasks: Sequence[BaseTask],
71
85
  dt: float,
72
86
  solver: str,
73
87
  damping: float = 1e-12,
@@ -75,7 +89,7 @@ def solve_ik(
75
89
  limits: Optional[Sequence[Limit]] = None,
76
90
  **kwargs,
77
91
  ) -> np.ndarray:
78
- """Solve the differential inverse kinematics problem.
92
+ r"""Solve the differential inverse kinematics problem.
79
93
 
80
94
  Computes a velocity tangent to the current robot configuration. The computed
81
95
  velocity satisfies at (weighted) best the set of provided kinematic tasks.
@@ -85,7 +99,9 @@ def solve_ik(
85
99
  tasks: List of kinematic tasks.
86
100
  dt: Integration timestep in [s].
87
101
  solver: Backend quadratic programming (QP) solver.
88
- damping: Levenberg-Marquardt damping.
102
+ damping: Levenberg-Marquardt damping applied to all tasks. Higher values
103
+ improve numerical stability but slow down task convergence. This
104
+ value applies to all dofs, including floating-base coordinates.
89
105
  safety_break: If True, stop execution and raise an exception if
90
106
  the current configuration is outside limits. If False, print a
91
107
  warning and continue execution.
@@ -93,13 +109,20 @@ def solve_ik(
93
109
  defaults to a configuration limit.
94
110
  kwargs: Keyword arguments to forward to the backend QP solver.
95
111
 
112
+ Raises:
113
+ NotWithinConfigurationLimits: If the current configuration is outside
114
+ the joint limits and `safety_break` is True.
115
+ NoSolutionFound: If the QP solver fails to find a solution.
116
+
96
117
  Returns:
97
- Velocity `v` in tangent space.
118
+ Velocity :math:`v` in tangent space.
98
119
  """
99
120
  configuration.check_limits(safety_break=safety_break)
100
121
  problem = build_ik(configuration, tasks, dt, damping, limits)
101
122
  result = qpsolvers.solve_problem(problem, solver=solver, **kwargs)
102
- dq = result.x
103
- assert dq is not None
104
- v: np.ndarray = dq / dt
123
+ if not result.found:
124
+ raise NoSolutionFound(solver)
125
+ delta_q = result.x
126
+ assert delta_q is not None
127
+ v: np.ndarray = delta_q / dt
105
128
  return v
mink/tasks/__init__.py CHANGED
@@ -3,20 +3,14 @@
3
3
  from .com_task import ComTask
4
4
  from .damping_task import DampingTask
5
5
  from .equality_constraint_task import EqualityConstraintTask
6
- from .exceptions import (
7
- InvalidConstraint,
8
- InvalidDamping,
9
- InvalidGain,
10
- InvalidTarget,
11
- TargetNotSet,
12
- TaskDefinitionError,
13
- )
14
6
  from .frame_task import FrameTask
7
+ from .kinetic_energy_regularization_task import KineticEnergyRegularizationTask
15
8
  from .posture_task import PostureTask
16
9
  from .relative_frame_task import RelativeFrameTask
17
- from .task import Objective, Task
10
+ from .task import BaseTask, Objective, Task
18
11
 
19
12
  __all__ = (
13
+ "BaseTask",
20
14
  "ComTask",
21
15
  "FrameTask",
22
16
  "Objective",
@@ -24,11 +18,6 @@ __all__ = (
24
18
  "PostureTask",
25
19
  "RelativeFrameTask",
26
20
  "Task",
27
- "TargetNotSet",
28
- "InvalidTarget",
29
- "TaskDefinitionError",
30
- "InvalidConstraint",
31
- "InvalidGain",
32
- "InvalidDamping",
33
21
  "EqualityConstraintTask",
22
+ "KineticEnergyRegularizationTask",
34
23
  )
mink/tasks/com_task.py CHANGED
@@ -9,7 +9,7 @@ import numpy as np
9
9
  import numpy.typing as npt
10
10
 
11
11
  from ..configuration import Configuration
12
- from .exceptions import InvalidTarget, TargetNotSet, TaskDefinitionError
12
+ from ..exceptions import InvalidTarget, TargetNotSet, TaskDefinitionError
13
13
  from .task import Task
14
14
 
15
15
 
@@ -18,6 +18,20 @@ class ComTask(Task):
18
18
 
19
19
  Attributes:
20
20
  target_com: Target position of the CoM.
21
+
22
+ Example:
23
+
24
+ .. code-block:: python
25
+
26
+ com_task = ComTask(model, cost=1.0)
27
+
28
+ # Update the target CoM directly.
29
+ com_desired = np.zeros(3)
30
+ com_task.set_target(com_desired)
31
+
32
+ # Or from a keyframe defined in the model.
33
+ configuration.update_from_keyframe("home")
34
+ com_task.set_target_from_configuration(configuration)
21
35
  """
22
36
 
23
37
  k: int = 3
@@ -35,6 +49,12 @@ class ComTask(Task):
35
49
  self.set_cost(cost)
36
50
 
37
51
  def set_cost(self, cost: npt.ArrayLike) -> None:
52
+ """Set the cost of the CoM task.
53
+
54
+ Args:
55
+ cost: A vector of shape (1,) (aka identical cost for all coordinates),
56
+ or (3,) (aka different costs for each coordinate).
57
+ """
38
58
  cost = np.atleast_1d(cost)
39
59
  if cost.ndim != 1 or cost.shape[0] not in (1, self.k):
40
60
  raise TaskDefinitionError(
@@ -50,7 +70,8 @@ class ComTask(Task):
50
70
  """Set the target CoM position in the world frame.
51
71
 
52
72
  Args:
53
- target_com: Desired center-of-mass position in the world frame.
73
+ target_com: A vector of shape (3,) representing the desired
74
+ center-of-mass position in the world frame.
54
75
  """
55
76
  target_com = np.atleast_1d(target_com)
56
77
  if target_com.ndim != 1 or target_com.shape[0] != (self.k):
@@ -69,7 +90,21 @@ class ComTask(Task):
69
90
  self.set_target(configuration.data.subtree_com[1])
70
91
 
71
92
  def compute_error(self, configuration: Configuration) -> np.ndarray:
72
- """Compute the CoM task error.
93
+ r"""Compute the CoM task error.
94
+
95
+ The center of mass :math:`c(q)` for a collection of bodies :math:`\mathcal{B}`
96
+ is the mass-weighted average of their individual centers of mass. After running
97
+ forward kinematics, in particular after calling ``mj_comPos``, MuJoCo stores
98
+ the CoM of each subtree in ``data.subtree_com``. This task uses the CoM of the
99
+ subtree starting from body 1, which is the entire robot excluding the world
100
+ body (body 0).
101
+
102
+ The task error :math:`e(q)` is the difference between the current CoM
103
+ :math:`c(q)` and the target CoM :math:`c^*`:
104
+
105
+ .. math::
106
+
107
+ e(q) = c(q) - c^*
73
108
 
74
109
  Args:
75
110
  configuration: Robot configuration :math:`q`.
@@ -79,19 +114,46 @@ class ComTask(Task):
79
114
  """
80
115
  if self.target_com is None:
81
116
  raise TargetNotSet(self.__class__.__name__)
117
+ # Note: body 0 is the world body, so we start from body 1 (the robot).
118
+ # TODO(kevin): Don't hardcode subtree index.
82
119
  return configuration.data.subtree_com[1] - self.target_com
83
120
 
84
121
  def compute_jacobian(self, configuration: Configuration) -> np.ndarray:
85
- """Compute the CoM task Jacobian.
122
+ r"""Compute the Jacobian of the CoM task error :math:`e(q)`.
123
+
124
+ The Jacobian is the derivative of this error with respect to the
125
+ generalized coordinates :math:`q`. Since the target :math:`c^*` is
126
+ constant, the Jacobian of the error simplifies to the Jacobian of the
127
+ CoM position :math:`c(q)`:
128
+
129
+ .. math::
130
+
131
+ J(q) = \frac{\partial e(q)}{\partial q} = \frac{\partial c(q)}{\partial q}
132
+
133
+ MuJoCo's ``mj_jacSubtreeCom`` function computes this Jacobian using the
134
+ formula:
135
+
136
+ .. math::
137
+
138
+ \frac{\partial c(q)}{\partial q} =
139
+ \frac{1}{M} \sum_{i \in \mathcal{B}} m_i \frac{\partial p_i(q)}{\partial q}
140
+ = \frac{1}{M} \sum_{i \in \mathcal{B}} m_i J_i(q)
141
+
142
+ where :math:`M = \sum_{i \in \mathcal{B}} m_i` is the total mass of the subtree,
143
+ :math:`m_i` is the mass of body :math:`i`, :math:`p_i(q)` is the position
144
+ of the origin of body frame :math:`i` in the world frame, :math:`J_i(q) =
145
+ \frac{\partial p_i(q)}{\partial q}` is the Jacobian mapping joint velocities to
146
+ the linear velocity of the origin of body frame :math:`i`, and the sum is over
147
+ all bodies :math:`\mathcal{B}` in the specified subtree (body 1 and its
148
+ descendants).
86
149
 
87
150
  Args:
88
151
  configuration: Robot configuration :math:`q`.
89
152
 
90
153
  Returns:
91
- Center-of-mass task jacobian :math:`J(q)`.
154
+ Jacobian of the center-of-mass task error :math:`J(q)`.
92
155
  """
93
- if self.target_com is None:
94
- raise TargetNotSet(self.__class__.__name__)
156
+ # NOTE: We don't need a target CoM to compute this Jacobian.
95
157
  jac = np.empty((self.k, configuration.nv))
96
158
  mujoco.mj_jacSubtreeCom(configuration.model, configuration.data, jac, 1)
97
159
  return jac
@@ -1,20 +1,66 @@
1
1
  """Damping task implementation."""
2
2
 
3
- from __future__ import annotations
4
-
5
3
  import mujoco
4
+ import numpy as np
6
5
  import numpy.typing as npt
7
6
 
7
+ from ..configuration import Configuration
8
8
  from .posture_task import PostureTask
9
9
 
10
10
 
11
11
  class DampingTask(PostureTask):
12
- """Minimize joint velocities.
12
+ r"""L2-regularization on joint velocities (a.k.a. *velocity damping*).
13
+
14
+ This task, typically used with a low priority in the task stack, adds a
15
+ Levenberg-Marquardt term to the quadratic program, favoring **minimum-norm
16
+ joint velocities** in redundant or near-singular situations. Formally, it
17
+ contributes the following term to the quadratic program:
18
+
19
+ .. math::
20
+ \frac{1}{2}\,\Delta \mathbf{q}^\top \Lambda\,\Delta \mathbf{q},
21
+
22
+ where :math:`\Delta \mathbf{q}\in\mathbb{R}^{n_v}` is the vector of joint
23
+ displacements and :math:`\Lambda = \mathrm{diag}(\lambda_1, \ldots, \lambda_{n_v})`
24
+ is a diagonal matrix of per-DoF damping weights specified via ``cost``. A larger
25
+ :math:`\lambda_i` reduces motion in DoF :math:`i`. With no other active
26
+ tasks, the robot remains at rest. Unlike the `damping` parameter in
27
+ :func:`~.solve_ik`, which is uniformly applied to all DoFs, this task does
28
+ not affect the floating-base coordinates.
29
+
30
+ .. note::
31
+
32
+ This task does not favor a particular posture, only small instantaneous motion.
33
+ If you need a posture bias, use :class:`~.PostureTask` instead.
34
+
35
+ Example:
13
36
 
14
- This task is implemented as a special case of the PostureTask where the gain and
15
- target configuration are set to 0 and qpos0 respectively.
37
+ .. code-block:: python
38
+
39
+ # Uniform damping across all degrees of freedom.
40
+ damping_task = DampingTask(model, cost=1.0)
41
+
42
+ # Custom damping.
43
+ cost = np.zeros(model.nv)
44
+ cost[:3] = 1.0 # High damping for the first 3 joints.
45
+ cost[3:] = 0.1 # Lower damping for the remaining joints.
46
+ damping_task = DampingTask(model, cost)
16
47
  """
17
48
 
18
49
  def __init__(self, model: mujoco.MjModel, cost: npt.ArrayLike):
19
- super().__init__(model=model, cost=cost, gain=0.0, lm_damping=0.0)
20
- self.target_q = model.qpos0
50
+ super().__init__(model, cost, gain=0.0, lm_damping=0.0)
51
+
52
+ def compute_error(self, configuration: Configuration) -> np.ndarray:
53
+ r"""Compute the damping task error.
54
+
55
+ The damping task does not chase a reference; its desired joint velocity
56
+ is identically **zero**, so the task error is always:
57
+
58
+ .. math:: e = \mathbf 0 \in \mathbb R^{n_v}.
59
+
60
+ Args:
61
+ configuration: Robot configuration :math:`q`.
62
+
63
+ Returns:
64
+ Zero vector of length :math:`n_v`.
65
+ """
66
+ return np.zeros(configuration.nv)
@@ -10,7 +10,7 @@ import numpy as np
10
10
  import numpy.typing as npt
11
11
 
12
12
  from ..configuration import Configuration
13
- from .exceptions import InvalidConstraint, TaskDefinitionError
13
+ from ..exceptions import InvalidConstraint, TaskDefinitionError
14
14
  from .task import Task
15
15
 
16
16
 
@@ -24,20 +24,54 @@ def _get_constraint_dim(constraint: int) -> int:
24
24
  }[constraint]
25
25
 
26
26
 
27
+ def _sparse2dense_fallback(
28
+ res: np.ndarray,
29
+ mat: np.ndarray,
30
+ rownnz: np.ndarray,
31
+ rowadr: np.ndarray,
32
+ colind: np.ndarray,
33
+ ) -> None:
34
+ """Fallback implementation of mujoco.mju_sparse2dense for Python 3.8.
35
+
36
+ This is a fallback implementation of mujoco.mju_sparse2dense for Python 3.8.
37
+ It is used when the version of MuJoCo is less than 3.2.5. This is because
38
+ mujoco.mju_sparse2dense was added Oct 23, 2024 which corresponds to 3.2.5+ and
39
+ Python 3.8 was dropped in 3.2.4.
40
+ """
41
+ # Ref: https://github.com/google-deepmind/mujoco/blob/178fb49c2b2ff48f59653515ab09b9cafca31b7a/src/engine/engine_util_sparse.c#L135
42
+ for r in range(res.shape[0]):
43
+ for i in range(rownnz[r]):
44
+ res[r, colind[rowadr[r] + i]] = mat[rowadr[r] + i]
45
+
46
+
27
47
  def _get_dense_constraint_jacobian(
28
48
  model: mujoco.MjModel, data: mujoco.MjData
29
49
  ) -> np.ndarray:
30
50
  """Return the dense constraint Jacobian for a model."""
31
- if mujoco.mj_isSparse(model):
32
- efc_J = np.empty((data.nefc, model.nv))
33
- mujoco.mju_sparse2dense(
34
- efc_J,
35
- data.efc_J,
36
- data.efc_J_rownnz,
37
- data.efc_J_rowadr,
38
- data.efc_J_colind,
39
- )
51
+
52
+ def _sparse2dense(data: mujoco.MjData) -> np.ndarray:
53
+ if mujoco.__version__ < "3.2.5":
54
+ efc_J = np.zeros((data.nefc, model.nv)) # Important to zero out here.
55
+ _sparse2dense_fallback(
56
+ efc_J,
57
+ data.efc_J,
58
+ data.efc_J_rownnz,
59
+ data.efc_J_rowadr,
60
+ data.efc_J_colind,
61
+ )
62
+ else:
63
+ efc_J = np.empty((data.nefc, model.nv))
64
+ mujoco.mju_sparse2dense(
65
+ efc_J,
66
+ data.efc_J,
67
+ data.efc_J_rownnz,
68
+ data.efc_J_rowadr,
69
+ data.efc_J_colind,
70
+ )
40
71
  return efc_J
72
+
73
+ if mujoco.mj_isSparse(model):
74
+ return _sparse2dense(data)
41
75
  return data.efc_J.reshape((data.nefc, model.nv)).copy()
42
76
 
43
77
 
@@ -53,9 +87,18 @@ class EqualityConstraintTask(Task):
53
87
  * ``mjEQ_JOINT``: Couple the values of two scalar joints.
54
88
  * ``mjEQ_TENDON``: Couple the values of two tendons.
55
89
 
56
- This task can regulate all equality constraints in a model or a specific subset
90
+ This task can regulate all equality constraints in the model or a specific subset
57
91
  identified by name or ID.
58
92
 
93
+ .. note::
94
+
95
+ MuJoCo computes the constraint residual and its Jacobian and stores them in
96
+ ``data.efc_pos`` and ``data.efc_J`` (potentially in sparse format), respectively.
97
+ The :func:`compute_error` and :func:`compute_jacobian` methods simply extract the
98
+ rows corresponding to the active equality constraints specified for this task
99
+ from ``data.efc_pos`` and ``data.efc_J``. More information on MuJoCo's constraint
100
+ model can be found in [MuJoCoEqualityConstraints]_.
101
+
59
102
  Attributes:
60
103
  equalities: ID or name of the equality constraints to regulate. If not provided,
61
104
  the task will regulate all equality constraints in the model.
@@ -126,32 +169,25 @@ class EqualityConstraintTask(Task):
126
169
  self.cost = np.repeat(self._cost, repeats)
127
170
 
128
171
  def compute_error(self, configuration: Configuration) -> np.ndarray:
129
- """Compute the equality constraint task error.
172
+ """Compute the task error (constraint residual) :math:`e(q) = r(q)`.
130
173
 
131
174
  Args:
132
175
  configuration: Robot configuration :math:`q`.
133
176
 
134
177
  Returns:
135
- Equality constraint task error vector :math:`e(q)`. The shape of the
136
- error vector is ``(neq_active * constraint_dim,)``, where ``neq_active``
137
- is the number of active equality constraints, and ``constraint_dim``
138
- depends on the type of equality constraint.
178
+ Task error vector :math:`e(q)` for the active equality constraints.
139
179
  """
140
180
  self._update_active_constraints(configuration)
141
181
  return configuration.data.efc_pos[self._mask]
142
182
 
143
183
  def compute_jacobian(self, configuration: Configuration) -> np.ndarray:
144
- """Compute the task Jacobian at a given configuration.
184
+ """Compute the task Jacobian (constraint Jacobian) :math:`J(q) = J_r(q)`.
145
185
 
146
186
  Args:
147
187
  configuration: Robot configuration :math:`q`.
148
188
 
149
189
  Returns:
150
- Equality constraint task jacobian :math:`J(q)`. The shape of the Jacobian
151
- is ``(neq_active * constraint_dim, nv)``, where ``neq_active`` is the
152
- number of active equality constraints, ``constraint_dim`` depends on the
153
- type of equality constraint, and ``nv`` is the dimension of the tangent
154
- space.
190
+ Task jacobian :math:`J(q)` for the active equality constraints.
155
191
  """
156
192
  self._update_active_constraints(configuration)
157
193
  efc_J = _get_dense_constraint_jacobian(configuration.model, configuration.data)
mink/tasks/frame_task.py CHANGED
@@ -8,19 +8,38 @@ import numpy as np
8
8
  import numpy.typing as npt
9
9
 
10
10
  from ..configuration import Configuration
11
+ from ..exceptions import TargetNotSet, TaskDefinitionError
11
12
  from ..lie import SE3
12
- from .exceptions import TargetNotSet, TaskDefinitionError
13
13
  from .task import Task
14
14
 
15
15
 
16
16
  class FrameTask(Task):
17
- """Regulate the pose of a frame expressed in the world frame.
17
+ """Regulate the position and orientation of a frame of interest on the robot.
18
18
 
19
19
  Attributes:
20
20
  frame_name: Name of the frame to regulate, typically the name of body, geom
21
21
  or site in the robot model.
22
22
  frame_type: The frame type: `body`, `geom` or `site`.
23
- transform_frame_to_world: Target pose of the frame.
23
+ transform_target_to_world: Target pose of the frame in the world frame.
24
+
25
+ Example:
26
+
27
+ .. code-block:: python
28
+
29
+ frame_task = FrameTask(
30
+ frame_name="target",
31
+ frame_type="site",
32
+ position_cost=1.0,
33
+ orientation_cost=1.0,
34
+ )
35
+
36
+ # Update the target pose directly.
37
+ transform_target_to_world = SE3.from_translation(np.random.rand(3))
38
+ frame_task.set_target(transform_target_to_world)
39
+
40
+ # Or from the current configuration. This will automatically compute the
41
+ # target pose from the current configuration and update the task target.
42
+ frame_task.set_target_from_configuration(configuration)
24
43
  """
25
44
 
26
45
  k: int = 6
@@ -0,0 +1,73 @@
1
+ """Kinetic energy regularization task implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional, SupportsFloat
6
+
7
+ import numpy as np
8
+
9
+ from ..configuration import Configuration
10
+ from ..exceptions import IntegrationTimestepNotSet, TaskDefinitionError
11
+ from .task import BaseTask, Objective
12
+
13
+
14
+ class KineticEnergyRegularizationTask(BaseTask):
15
+ r"""Kinetic-energy regularization.
16
+
17
+ This task, often used with a low priority in the task stack, penalizes the system's
18
+ kinetic energy. Formally, it contributes the following term to the quadratic
19
+ program:
20
+
21
+ .. math::
22
+ \frac{1}{2}\, \lambda\, \Delta \mathbf{q}^\top M(\mathbf{q})\,\Delta \mathbf{q},
23
+
24
+ where :math:`\Delta \mathbf{q}\in\mathbb{R}^{n_v}` is the vector of joint
25
+ displacements, :math:`M(\mathbf{q})` is the joint-space inertia matrix, and
26
+ :math:`\lambda` is the scalar strength of the regularization.
27
+
28
+ .. note::
29
+
30
+ This task can be seen as an inertia-weighted version of the
31
+ :class:`~.DampingTask`. Degrees of freedom with higher inertia will move less
32
+ for the same cost.
33
+
34
+ .. warning::
35
+
36
+ The integration timestep :math:`\Delta t` must be set via :meth:`set_dt`
37
+ before use. This ensures the cost is expressed in units of energy (Joules).
38
+
39
+ Example:
40
+
41
+ .. code-block:: python
42
+
43
+ task = KineticEnergyRegularizationTask(cost=1e-4)
44
+ task.set_dt(0.02)
45
+ """
46
+
47
+ def __init__(self, cost: SupportsFloat):
48
+ cost = float(cost)
49
+ if cost < 0:
50
+ raise TaskDefinitionError(f"{self.__class__.__name__} cost should be >= 0")
51
+ self.cost: float = cost
52
+
53
+ # Kinetic energy is defined as T = ½ * q̇ᵀ * M * q̇. Since q̇ ≈ Δq / dt, we
54
+ # substitute: T ≈ ½ * (Δq / dt)ᵀ * M * (Δq / dt) = ½ * Δqᵀ * (M / dt²) * Δq.
55
+ # Therefore, we scale the inertia matrix by 1 / dt² to penalize energy in
56
+ # terms of joint displacements Δq.
57
+ self.inv_dt_sq: Optional[float] = None
58
+
59
+ def set_dt(self, dt: float) -> None:
60
+ """Set the integration timestep.
61
+
62
+ Args:
63
+ dt: Integration timestep in [s].
64
+ """
65
+ self.inv_dt_sq = 1.0 / dt**2
66
+
67
+ def compute_qp_objective(self, configuration: Configuration) -> Objective:
68
+ if self.inv_dt_sq is None:
69
+ raise IntegrationTimestepNotSet(self.__class__.__name__)
70
+ M = configuration.get_inertia_matrix()
71
+ H = self.cost * self.inv_dt_sq * M
72
+ c = np.zeros(configuration.nv)
73
+ return Objective(H, c)
@@ -9,19 +9,36 @@ import numpy as np
9
9
  import numpy.typing as npt
10
10
 
11
11
  from ..configuration import Configuration
12
+ from ..exceptions import InvalidTarget, TargetNotSet, TaskDefinitionError
12
13
  from ..utils import get_freejoint_dims
13
- from .exceptions import InvalidTarget, TargetNotSet, TaskDefinitionError
14
14
  from .task import Task
15
15
 
16
16
 
17
17
  class PostureTask(Task):
18
- """Regulate the joint angles of the robot towards a desired posture.
18
+ """Regulate joint angles towards a target posture.
19
19
 
20
- A posture is a vector of actuated joint angles. Floating-base coordinates are not
21
- affected by this task.
20
+ Often used with a low priority in the task stack, this task acts like a regularizer,
21
+ biasing the solution toward a specific joint configuration.
22
22
 
23
23
  Attributes:
24
- target_q: Target configuration.
24
+ target_q: Target configuration :math:`q^*`, of shape :math:`(n_q,)`. Units are
25
+ radians for revolute joints and meters for prismatic joints. Note that
26
+ floating-base coordinates are not affected by this task but should be
27
+ included in the target configuration.
28
+
29
+ Example:
30
+
31
+ .. code-block:: python
32
+
33
+ posture_task = PostureTask(model, cost=1e-3)
34
+
35
+ # Update the target posture directly.
36
+ q_desired = ...
37
+ posture_task.set_target(q_desired)
38
+
39
+ # Or from a keyframe defined in the model.
40
+ configuration.update_from_keyframe("home")
41
+ posture_task.set_target_from_configuration(configuration)
25
42
  """
26
43
 
27
44
  target_q: Optional[np.ndarray]
@@ -66,7 +83,8 @@ class PostureTask(Task):
66
83
  """Set the target posture.
67
84
 
68
85
  Args:
69
- target_q: Desired joint configuration.
86
+ target_q: A vector of shape (nq,) representing the desired joint
87
+ configuration.
70
88
  """
71
89
  target_q = np.atleast_1d(target_q)
72
90
  if target_q.ndim != 1 or target_q.shape[0] != (self.nq):
@@ -77,7 +95,7 @@ class PostureTask(Task):
77
95
  self.target_q = target_q.copy()
78
96
 
79
97
  def set_target_from_configuration(self, configuration: Configuration) -> None:
80
- """Set the target posture from the current configuration.
98
+ """Set the target posture by extracting it from the current configuration.
81
99
 
82
100
  Args:
83
101
  configuration: Robot configuration :math:`q`.
@@ -108,8 +126,8 @@ class PostureTask(Task):
108
126
  m=configuration.model,
109
127
  qvel=qvel,
110
128
  dt=1.0,
111
- qpos1=configuration.q,
112
- qpos2=self.target_q,
129
+ qpos1=self.target_q,
130
+ qpos2=configuration.q,
113
131
  )
114
132
 
115
133
  if self._v_ids is not None:
@@ -132,11 +150,7 @@ class PostureTask(Task):
132
150
  Returns:
133
151
  Posture task jacobian :math:`J(q)`.
134
152
  """
135
- if self.target_q is None:
136
- raise TargetNotSet(self.__class__.__name__)
137
-
138
- jac = -np.eye(configuration.nv)
153
+ jac = np.eye(configuration.nv)
139
154
  if self._v_ids is not None:
140
155
  jac[:, self._v_ids] = 0.0
141
-
142
156
  return jac
@@ -8,8 +8,8 @@ import numpy as np
8
8
  import numpy.typing as npt
9
9
 
10
10
  from ..configuration import Configuration
11
+ from ..exceptions import TargetNotSet, TaskDefinitionError
11
12
  from ..lie import SE3
12
- from .exceptions import TargetNotSet, TaskDefinitionError
13
13
  from .task import Task
14
14
 
15
15
 
mink/tasks/task.py CHANGED
@@ -6,23 +6,41 @@ from typing import NamedTuple
6
6
  import numpy as np
7
7
 
8
8
  from ..configuration import Configuration
9
- from .exceptions import InvalidDamping, InvalidGain
9
+ from ..exceptions import InvalidDamping, InvalidGain
10
10
 
11
11
 
12
12
  class Objective(NamedTuple):
13
- r"""Quadratic objective of the form :math:`\frac{1}{2} x^T H x + c^T x`."""
13
+ r"""Quadratic objective of the form
14
+ :math:`\frac{1}{2} \Delta q^T H \Delta q + c^T \Delta q`.
15
+ """
14
16
 
15
17
  H: np.ndarray
16
- """Hessian matrix, of shape (n_v, n_v)"""
18
+ r"""Hessian matrix, of shape (:math:`n_v`, :math:`n_v`)."""
17
19
  c: np.ndarray
18
- """Linear vector, of shape (n_v,)."""
20
+ r"""Linear vector, of shape (:math:`n_v`,)."""
19
21
 
20
22
  def value(self, x: np.ndarray) -> float:
21
23
  """Returns the value of the objective at the input vector."""
22
24
  return x.T @ self.H @ x + self.c @ x
23
25
 
24
26
 
25
- class Task(abc.ABC):
27
+ class BaseTask(abc.ABC):
28
+ """Base class for all tasks."""
29
+
30
+ @abc.abstractmethod
31
+ def compute_qp_objective(self, configuration: Configuration) -> Objective:
32
+ r"""Compute the matrix-vector pair :math:`(H, c)` of the QP objective.
33
+
34
+ Args:
35
+ configuration: Robot configuration :math:`q`.
36
+
37
+ Returns:
38
+ Pair :math:`(H(q), c(q))`.
39
+ """
40
+ raise NotImplementedError
41
+
42
+
43
+ class Task(BaseTask):
26
44
  r"""Abstract base class for kinematic tasks.
27
45
 
28
46
  Subclasses must implement the configuration-dependent task error
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mink
3
- Version: 0.0.10
3
+ Version: 0.0.12
4
4
  Summary: mink: MuJoCo inverse kinematics.
5
5
  Keywords: inverse,kinematics,mujoco
6
6
  Author-email: Kevin Zakka <zakka@berkeley.edu>
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.9
16
16
  Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
19
20
  Classifier: Topic :: Scientific/Engineering
20
21
  License-File: LICENSE
21
22
  Requires-Dist: mujoco >= 3.1.6
@@ -27,7 +28,6 @@ Requires-Dist: black ; extra == "dev"
27
28
  Requires-Dist: mink[test] ; extra == "dev"
28
29
  Requires-Dist: mypy ; extra == "dev"
29
30
  Requires-Dist: ruff ; extra == "dev"
30
- Requires-Dist: dm_control >= 1.0.20 ; extra == "examples"
31
31
  Requires-Dist: loop-rate-limiters >= 0.1.0 ; extra == "examples"
32
32
  Requires-Dist: qpsolvers[daqp] >= 4.3.1 ; extra == "examples"
33
33
  Requires-Dist: osqp >=0.6.2,<1 ; extra == "examples"
@@ -59,6 +59,7 @@ Features include:
59
59
  * Task specification in configuration or operational space;
60
60
  * Limits on joint positions and velocities;
61
61
  * Collision avoidance between any geom pair;
62
+ * Support for closed-chain kinematics (loop closures) via [equality constraints](https://mujoco.readthedocs.io/en/stable/computation/index.html#coequality);
62
63
  * Lie group interface for rigid body transformations.
63
64
 
64
65
  For usage and API reference, see the [documentation](https://kevinzakka.github.io/mink/).
@@ -66,14 +67,14 @@ For usage and API reference, see the [documentation](https://kevinzakka.github.i
66
67
  If you use mink in your research, please cite it as follows:
67
68
 
68
69
  ```bibtex
69
- @software{Zakka_Mink_Python_inverse_2024,
70
+ @software{Zakka_Mink_Python_inverse_2025,
70
71
  author = {Zakka, Kevin},
71
- license = {Apache-2.0},
72
- month = jul,
73
72
  title = {{Mink: Python inverse kinematics based on MuJoCo}},
73
+ year = {2025},
74
+ month = may,
75
+ version = {0.0.11},
74
76
  url = {https://github.com/kevinzakka/mink},
75
- version = {0.0.4},
76
- year = {2024}
77
+ license = {Apache-2.0}
77
78
  }
78
79
  ```
79
80
 
@@ -1,9 +1,9 @@
1
- mink/__init__.py,sha256=g-eOZoPMgO3l4PtP7RK4ercpo1lyLnIcOT6Tb78T968,1829
2
- mink/configuration.py,sha256=wbP2ab-Zn5pH844Czk5w8mgDXP44AosMOdNxqwyCSik,9199
1
+ mink/__init__.py,sha256=511PwU7HxnllhOlVaHDF5FtSvx7NzohzYw-KkUWOh9M,2285
2
+ mink/configuration.py,sha256=QGh99QRVJ4sUEj0rSjz6oJBk05_VAHACWCop2wPe_fI,10136
3
3
  mink/constants.py,sha256=hcWy4zM4hGI7vvJrTmCjJgs2WtOxKJ1IVtYHucWbOAI,813
4
- mink/exceptions.py,sha256=tmExo-ydn_mu9Mny5sP0Ehr7hfo3d79fM8HvvxZBKYM,2931
4
+ mink/exceptions.py,sha256=-O4mlcOqLgwTfMG-qmWl35hCPnhiPAufr3ioxMD1yWA,4255
5
5
  mink/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- mink/solve_ik.py,sha256=VzPYpfxkJ-JxUZIpwN7-WVOw0Oi2YiIvDZcwB5ROeug,3591
6
+ mink/solve_ik.py,sha256=ZP_z4kiPaa_77PO7hQfRlO6nX5Y4B4qrfzUfR8I1ruU,4622
7
7
  mink/utils.py,sha256=Eok-pr8xR4LjmjEDEOtD7pvZPDCk53K9gWGdth8TxOA,5329
8
8
  mink/.mypy_cache/.gitignore,sha256=amnaZw0RUw038PDP3HvtMLeOpkNOJPenMgi5guKdMiw,34
9
9
  mink/.mypy_cache/CACHEDIR.TAG,sha256=8cE6_FVTWMkDOw8fMKqhd_6IvaQPS4okWYQA1UeHatw,190
@@ -333,25 +333,24 @@ mink/contrib/keyboard_teleop/keycodes.py,sha256=1AN3bWpkw99ZjrkIQnu8QdHSz-_boWMY
333
333
  mink/contrib/keyboard_teleop/teleop_mocap.py,sha256=2cuauiBHsQb6IVruRbGtCumdHQazEGvt9w2L50eKGzw,8095
334
334
  mink/lie/__init__.py,sha256=7tm3ZFnF3o1SDd9MOFO1In13lHMQJHO0FB-ejI6tsgE,202
335
335
  mink/lie/base.py,sha256=8TGWWEWRvmoBsJP-9y1YXQHkTC-TFfI8JDvBFKxxP-A,4865
336
- mink/lie/se3.py,sha256=cEzAzKEfBAQcbHhbZbhW8QZA8WpfRRaeZ0YGdSmhOx8,8634
337
- mink/lie/so3.py,sha256=Goipwo7PRxNecYRNBmueWCiO2D51D7qHlzs2gQiXO-Q,8423
336
+ mink/lie/se3.py,sha256=0nrhSE0zgacRBDGDryGTRNLVd9mff-WU4qmBlK6cbdE,10069
337
+ mink/lie/so3.py,sha256=3zmuO31DRXspmBBbD8bOlxYXrb3SZWPg72L_Es1aEYs,9246
338
338
  mink/lie/utils.py,sha256=DuEl3pj84daLvMKN84-YBvkXnJfqvc5vpvJ9J-pJ11U,403
339
- mink/limits/__init__.py,sha256=hX5Dgpri9AE6YtUCkW79AXMBuNAuBhniR9kQ6Rxwv3Y,416
339
+ mink/limits/__init__.py,sha256=-ijdhSGfMRVE1o1pJ-3qTSsSNIIVxhdk2Z511_UzjzU,343
340
340
  mink/limits/collision_avoidance_limit.py,sha256=MJXesSjvm_2O6RUEz2e4D8uTVOHyTsH9j4UxkPemcn8,11918
341
- mink/limits/configuration_limit.py,sha256=WspxM4W3-VFIKVo8FLg68x7T9EMmfRmzefiWmi4qrZY,4376
342
- mink/limits/exceptions.py,sha256=EnVKgFhUJFM6rNVCtdngb-XMu66PWKMFSDy-XkdKzr8,178
341
+ mink/limits/configuration_limit.py,sha256=y_1mWbEeuprjcuAgJnFOKdWNI-KqxTPA9X0pW9MF9zQ,4377
343
342
  mink/limits/limit.py,sha256=65K4clGFCPxa3TScGaLhYe1rVYgmJnkMnsFg6DCGjI4,1529
344
- mink/limits/velocity_limit.py,sha256=2i1Jsh6Y4_qw6CR5_hNxQr-X1YkfJtfeLqlsuqj2b8k,3744
345
- mink/tasks/__init__.py,sha256=rTx9Ab8XPSohhSa1RUD7lNggTzVi02LOaPzPZFMFvuI,763
346
- mink/tasks/com_task.py,sha256=ch6w7pwa5E0GVo2DCAuEF-c8TCN1iMk-ySY9A4B6yoQ,3121
347
- mink/tasks/damping_task.py,sha256=SwSfUvMHzoNYNsloQRD1qlPLd4kfoDIWZlTDU9LuKM4,556
348
- mink/tasks/equality_constraint_task.py,sha256=Ou8jBSC1SsZCi_WbzkgdYog4-rXsUF2uMmwGpFSlBZU,8196
349
- mink/tasks/exceptions.py,sha256=d2P9q_CWaHUXxgSwFjcu-ml87mLuXJYxrVLUwrJp6w8,803
350
- mink/tasks/frame_task.py,sha256=DeNg-bEJxqnrRI0FaJK4UHyb8CyF4A7uPF9G-CdN0sg,5307
351
- mink/tasks/posture_task.py,sha256=sVDZyalCh5DP8zmCuX5kYuZxiMxRut_mTZUjv5y3b5M,3998
352
- mink/tasks/relative_frame_task.py,sha256=9rIiYocI1eEESt0bLZZpQR7K6ggVRZH8iEhdhzkfZa0,5119
353
- mink/tasks/task.py,sha256=F16YZT1L9ueNOcKoOhbCyEnZw0DOgrmjqADl0jahVQI,4838
354
- mink-0.0.10.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
355
- mink-0.0.10.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
356
- mink-0.0.10.dist-info/METADATA,sha256=AJQA0xMmwAZiWYmLgDDQo1rKDBQ7AYJxCUMtd-6nlik,5607
357
- mink-0.0.10.dist-info/RECORD,,
343
+ mink/limits/velocity_limit.py,sha256=drb5tyOymUb_erlghfH7pAZEzfirfvsII1IPrUahSS4,3745
344
+ mink/tasks/__init__.py,sha256=W-wUUrjbnUO17YQpP8Bodm-8p5GJfwTCALS58AroH5Y,624
345
+ mink/tasks/com_task.py,sha256=JZoRYiT0F1GObIW8V8gEYELUTpOa681HsrFljvp6heU,5768
346
+ mink/tasks/damping_task.py,sha256=evjHJNibPj8D3Uq3GXuTuGLC03fjeVk8oYBmZm5jtRM,2407
347
+ mink/tasks/equality_constraint_task.py,sha256=ICC_vN2LB6Sv3T3QxCmbVwQNvhmjtDVaB7-a-PYUNS4,9509
348
+ mink/tasks/frame_task.py,sha256=2Zd7Sts5C_eGGJ64XYQZ4uZNYvS2PFm347uqSb_Y6Lc,5962
349
+ mink/tasks/kinetic_energy_regularization_task.py,sha256=VpAVA4fpDAZNY96Gx7R2Nim-CDj665sXk0FkfINb00g,2544
350
+ mink/tasks/posture_task.py,sha256=14xFUk02ar14gAMUtKr-ZhGDjylcZkCNYUXSFcM7g4A,4643
351
+ mink/tasks/relative_frame_task.py,sha256=Bu9vBBqzhLKZyBPj1Xc0b69dAa1twkzeLknZDVcRmoA,5120
352
+ mink/tasks/task.py,sha256=BXz17rL_KXiGBrS_5ITqMj2dQXzoQMUUN3xXfGGtJRA,5319
353
+ mink-0.0.12.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
354
+ mink-0.0.12.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
355
+ mink-0.0.12.dist-info/METADATA,sha256=8_3KFjIICLxr9v9CmVzmoGDEPkBu3BACKJOkvC0qJ4I,5758
356
+ mink-0.0.12.dist-info/RECORD,,
mink/limits/exceptions.py DELETED
@@ -1,7 +0,0 @@
1
- """Exceptions raised by limits."""
2
-
3
- from ..exceptions import MinkError
4
-
5
-
6
- class LimitDefinitionError(MinkError):
7
- """Exception raised when a limit definition is ill-formed."""
mink/tasks/exceptions.py DELETED
@@ -1,31 +0,0 @@
1
- """Exceptions raised by tasks."""
2
-
3
- from ..exceptions import MinkError
4
-
5
-
6
- class TaskDefinitionError(MinkError):
7
- """Exception raised when a task definition is ill-formed."""
8
-
9
-
10
- class TargetNotSet(MinkError):
11
- """Exception raised when attempting to use a task with an unset target."""
12
-
13
- def __init__(self, cls_name: str):
14
- message = f"No target set for {cls_name}"
15
- super().__init__(message)
16
-
17
-
18
- class InvalidTarget(MinkError):
19
- """Exception raised when the target is invalid."""
20
-
21
-
22
- class InvalidGain(MinkError):
23
- """Exception raised when the gain is outside the valid range."""
24
-
25
-
26
- class InvalidDamping(MinkError):
27
- """Exception raised when the damping is outside the valid range."""
28
-
29
-
30
- class InvalidConstraint(MinkError):
31
- """Exception raised when a constraint is invalid."""
File without changes