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 +12 -3
- mink/configuration.py +67 -30
- mink/lie/__init__.py +1 -2
- mink/lie/base.py +2 -2
- mink/lie/so3.py +2 -0
- mink/lie/utils.py +2 -10
- mink/limits/collision_avoidance_limit.py +46 -63
- mink/limits/configuration_limit.py +23 -30
- mink/limits/limit.py +27 -9
- mink/limits/velocity_limit.py +30 -23
- mink/solve_ik.py +2 -2
- mink/tasks/__init__.py +4 -0
- mink/tasks/com_task.py +24 -9
- mink/tasks/damping_task.py +20 -0
- mink/tasks/frame_task.py +45 -13
- mink/tasks/posture_task.py +53 -14
- mink/tasks/relative_frame_task.py +142 -0
- mink/tasks/task.py +61 -28
- mink/utils.py +15 -7
- {mink-0.0.1.dist-info → mink-0.0.3.dist-info}/METADATA +37 -9
- {mink-0.0.1.dist-info → mink-0.0.3.dist-info}/RECORD +22 -24
- mink/lie/tests/__init__.py +0 -0
- mink/lie/tests/test_axioms.py +0 -51
- mink/lie/tests/test_operations.py +0 -77
- mink/lie/tests/utils.py +0 -20
- {mink-0.0.1.dist-info → mink-0.0.3.dist-info}/WHEEL +0 -0
mink/tasks/com_task.py
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
"""Center
|
1
|
+
"""Center-of-mass task implementation."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
2
4
|
|
3
5
|
from typing import Optional
|
4
6
|
|
@@ -12,7 +14,7 @@ from .task import Task
|
|
12
14
|
|
13
15
|
|
14
16
|
class ComTask(Task):
|
15
|
-
"""Regulate the center
|
17
|
+
"""Regulate the center-of-mass (CoM) of a robot.
|
16
18
|
|
17
19
|
Attributes:
|
18
20
|
target_com: Target position of the CoM.
|
@@ -33,7 +35,6 @@ class ComTask(Task):
|
|
33
35
|
self.set_cost(cost)
|
34
36
|
|
35
37
|
def set_cost(self, cost: npt.ArrayLike) -> None:
|
36
|
-
"""Set a new cost for all CoM coordinates."""
|
37
38
|
cost = np.atleast_1d(cost)
|
38
39
|
if cost.ndim != 1 or cost.shape[0] not in (1, self.k):
|
39
40
|
raise TaskDefinitionError(
|
@@ -46,7 +47,11 @@ class ComTask(Task):
|
|
46
47
|
self.cost[:] = cost
|
47
48
|
|
48
49
|
def set_target(self, target_com: npt.ArrayLike) -> None:
|
49
|
-
"""Set the target CoM position in the world frame.
|
50
|
+
"""Set the target CoM position in the world frame.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
target_com: Desired center-of-mass position in the world frame.
|
54
|
+
"""
|
50
55
|
target_com = np.atleast_1d(target_com)
|
51
56
|
if target_com.ndim != 1 or target_com.shape[0] != (self.k):
|
52
57
|
raise InvalidTarget(
|
@@ -56,14 +61,21 @@ class ComTask(Task):
|
|
56
61
|
self.target_com = target_com.copy()
|
57
62
|
|
58
63
|
def set_target_from_configuration(self, configuration: Configuration) -> None:
|
59
|
-
"""Set the target CoM from a given robot configuration.
|
64
|
+
"""Set the target CoM from a given robot configuration.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
configuration: Robot configuration :math:`q`.
|
68
|
+
"""
|
60
69
|
self.set_target(configuration.data.subtree_com[1])
|
61
70
|
|
62
71
|
def compute_error(self, configuration: Configuration) -> np.ndarray:
|
63
72
|
"""Compute the CoM task error.
|
64
73
|
|
65
|
-
|
66
|
-
|
74
|
+
Args:
|
75
|
+
configuration: Robot configuration :math:`q`.
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
Center-of-mass task error vector :math:`e(q)`.
|
67
79
|
"""
|
68
80
|
if self.target_com is None:
|
69
81
|
raise TargetNotSet(self.__class__.__name__)
|
@@ -72,8 +84,11 @@ class ComTask(Task):
|
|
72
84
|
def compute_jacobian(self, configuration: Configuration) -> np.ndarray:
|
73
85
|
"""Compute the CoM task Jacobian.
|
74
86
|
|
75
|
-
|
76
|
-
|
87
|
+
Args:
|
88
|
+
configuration: Robot configuration :math:`q`.
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
Center-of-mass task jacobian :math:`J(q)`.
|
77
92
|
"""
|
78
93
|
if self.target_com is None:
|
79
94
|
raise TargetNotSet(self.__class__.__name__)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"""Damping task implementation."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import mujoco
|
6
|
+
import numpy.typing as npt
|
7
|
+
|
8
|
+
from .posture_task import PostureTask
|
9
|
+
|
10
|
+
|
11
|
+
class DampingTask(PostureTask):
|
12
|
+
"""Minimize joint velocities.
|
13
|
+
|
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.
|
16
|
+
"""
|
17
|
+
|
18
|
+
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
|
mink/tasks/frame_task.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
"""Frame task implementation."""
|
2
2
|
|
3
|
+
from __future__ import annotations
|
4
|
+
|
3
5
|
from typing import Optional
|
4
6
|
|
5
7
|
import numpy as np
|
@@ -12,12 +14,13 @@ from .task import Task
|
|
12
14
|
|
13
15
|
|
14
16
|
class FrameTask(Task):
|
15
|
-
"""Regulate the pose of a
|
17
|
+
"""Regulate the pose of a frame expressed in the world frame.
|
16
18
|
|
17
19
|
Attributes:
|
18
|
-
frame_name: Name of the frame to regulate
|
19
|
-
|
20
|
-
|
20
|
+
frame_name: Name of the frame to regulate, typically the name of body, geom
|
21
|
+
or site in the robot model.
|
22
|
+
frame_type: The frame type: `body`, `geom` or `site`.
|
23
|
+
transform_frame_to_world: Target pose of the frame.
|
21
24
|
"""
|
22
25
|
|
23
26
|
k: int = 6
|
@@ -71,21 +74,44 @@ class FrameTask(Task):
|
|
71
74
|
self.cost[3:] = orientation_cost
|
72
75
|
|
73
76
|
def set_target(self, transform_target_to_world: SE3) -> None:
|
74
|
-
"""Set the target pose
|
77
|
+
"""Set the target pose.
|
78
|
+
|
79
|
+
Args:
|
80
|
+
transform_target_to_world: Transform from the task target frame to the
|
81
|
+
world frame.
|
82
|
+
"""
|
75
83
|
self.transform_target_to_world = transform_target_to_world.copy()
|
76
84
|
|
77
85
|
def set_target_from_configuration(self, configuration: Configuration) -> None:
|
78
|
-
"""Set the target pose from a given robot configuration.
|
86
|
+
"""Set the target pose from a given robot configuration.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
configuration: Robot configuration :math:`q`.
|
90
|
+
"""
|
79
91
|
self.set_target(
|
80
92
|
configuration.get_transform_frame_to_world(self.frame_name, self.frame_type)
|
81
93
|
)
|
82
94
|
|
83
95
|
def compute_error(self, configuration: Configuration) -> np.ndarray:
|
84
|
-
"""Compute the frame task error.
|
96
|
+
r"""Compute the frame task error.
|
97
|
+
|
98
|
+
This error is a twist :math:`e(q) \in se(3)` expressed in the local frame,
|
99
|
+
i.e., it is a body twist. It is computed by taking the right-minus difference
|
100
|
+
between the target pose :math:`T_{0t}` and current frame pose :math:`T_{0b}`:
|
85
101
|
|
86
|
-
|
87
|
-
|
88
|
-
|
102
|
+
.. math::
|
103
|
+
|
104
|
+
e(q) := {}_b \xi_{0b} = -(T_{t0} \ominus T_{b0})
|
105
|
+
= -\log(T_{t0} \cdot T_{0b}) = -\log(T_{tb}) = \log(T_{bt})
|
106
|
+
|
107
|
+
where :math:`b` denotes our frame, :math:`t` the target frame and
|
108
|
+
:math:`0` the inertial frame.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
configuration: Robot configuration :math:`q`.
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
Frame task error vector :math:`e(q)`.
|
89
115
|
"""
|
90
116
|
if self.transform_target_to_world is None:
|
91
117
|
raise TargetNotSet(self.__class__.__name__)
|
@@ -96,10 +122,16 @@ class FrameTask(Task):
|
|
96
122
|
return self.transform_target_to_world.minus(transform_frame_to_world)
|
97
123
|
|
98
124
|
def compute_jacobian(self, configuration: Configuration) -> np.ndarray:
|
99
|
-
"""Compute the frame task Jacobian.
|
125
|
+
r"""Compute the frame task Jacobian.
|
126
|
+
|
127
|
+
The derivation of the formula for this Jacobian is detailed in
|
128
|
+
[FrameTaskJacobian]_.
|
129
|
+
|
130
|
+
Args:
|
131
|
+
configuration: Robot configuration :math:`q`.
|
100
132
|
|
101
|
-
|
102
|
-
|
133
|
+
Returns:
|
134
|
+
Frame task jacobian :math:`J(q)`.
|
103
135
|
"""
|
104
136
|
if self.transform_target_to_world is None:
|
105
137
|
raise TargetNotSet(self.__class__.__name__)
|
mink/tasks/posture_task.py
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
"""Posture task implementation."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
1
5
|
from typing import Optional
|
2
6
|
|
3
7
|
import mujoco
|
@@ -11,7 +15,7 @@ from .task import Task
|
|
11
15
|
|
12
16
|
|
13
17
|
class PostureTask(Task):
|
14
|
-
"""Regulate joint angles
|
18
|
+
"""Regulate the joint angles of the robot towards a desired posture.
|
15
19
|
|
16
20
|
A posture is a vector of actuated joint angles. Floating-base coordinates are not
|
17
21
|
affected by this task.
|
@@ -25,15 +29,12 @@ class PostureTask(Task):
|
|
25
29
|
def __init__(
|
26
30
|
self,
|
27
31
|
model: mujoco.MjModel,
|
28
|
-
cost:
|
32
|
+
cost: npt.ArrayLike,
|
29
33
|
gain: float = 1.0,
|
30
34
|
lm_damping: float = 0.0,
|
31
35
|
):
|
32
|
-
if cost < 0.0:
|
33
|
-
raise TaskDefinitionError(f"{self.__class__.__name__} cost must be >= 0")
|
34
|
-
|
35
36
|
super().__init__(
|
36
|
-
cost=np.
|
37
|
+
cost=np.zeros((model.nv,)),
|
37
38
|
gain=gain,
|
38
39
|
lm_damping=lm_damping,
|
39
40
|
)
|
@@ -48,9 +49,25 @@ class PostureTask(Task):
|
|
48
49
|
|
49
50
|
self.k = model.nv
|
50
51
|
self.nq = model.nq
|
52
|
+
self.set_cost(cost)
|
53
|
+
|
54
|
+
def set_cost(self, cost: npt.ArrayLike) -> None:
|
55
|
+
cost = np.atleast_1d(cost)
|
56
|
+
if cost.ndim != 1 or cost.shape[0] not in (1, self.k):
|
57
|
+
raise TaskDefinitionError(
|
58
|
+
f"{self.__class__.__name__} cost must be a vector of shape (1,) "
|
59
|
+
f"(aka identical cost for all dofs) or ({self.k},). Got {cost.shape}"
|
60
|
+
)
|
61
|
+
if not np.all(cost >= 0.0):
|
62
|
+
raise TaskDefinitionError(f"{self.__class__.__name__} cost should be >= 0")
|
63
|
+
self.cost[: self.k] = cost
|
51
64
|
|
52
65
|
def set_target(self, target_q: npt.ArrayLike) -> None:
|
53
|
-
"""Set the target posture.
|
66
|
+
"""Set the target posture.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
target_q: Desired joint configuration.
|
70
|
+
"""
|
54
71
|
target_q = np.atleast_1d(target_q)
|
55
72
|
if target_q.ndim != 1 or target_q.shape[0] != (self.nq):
|
56
73
|
raise InvalidTarget(
|
@@ -60,14 +77,27 @@ class PostureTask(Task):
|
|
60
77
|
self.target_q = target_q.copy()
|
61
78
|
|
62
79
|
def set_target_from_configuration(self, configuration: Configuration) -> None:
|
63
|
-
"""Set the target posture from the current configuration.
|
80
|
+
"""Set the target posture from the current configuration.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
configuration: Robot configuration :math:`q`.
|
84
|
+
"""
|
64
85
|
self.set_target(configuration.q)
|
65
86
|
|
66
87
|
def compute_error(self, configuration: Configuration) -> np.ndarray:
|
67
|
-
"""Compute the posture task error.
|
88
|
+
r"""Compute the posture task error.
|
68
89
|
|
69
|
-
The error is defined as
|
70
|
-
|
90
|
+
The error is defined as:
|
91
|
+
|
92
|
+
.. math::
|
93
|
+
|
94
|
+
e(q) = q^* \ominus q
|
95
|
+
|
96
|
+
Args:
|
97
|
+
configuration: Robot configuration :math:`q`.
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
Posture task error vector :math:`e(q)`.
|
71
101
|
"""
|
72
102
|
if self.target_q is None:
|
73
103
|
raise TargetNotSet(self.__class__.__name__)
|
@@ -88,10 +118,19 @@ class PostureTask(Task):
|
|
88
118
|
return qvel
|
89
119
|
|
90
120
|
def compute_jacobian(self, configuration: Configuration) -> np.ndarray:
|
91
|
-
"""Compute the posture task Jacobian.
|
121
|
+
r"""Compute the posture task Jacobian.
|
122
|
+
|
123
|
+
The task Jacobian is defined as:
|
124
|
+
|
125
|
+
.. math::
|
126
|
+
|
127
|
+
J(q) = I_{n_v}
|
128
|
+
|
129
|
+
Args:
|
130
|
+
configuration: Robot configuration :math:`q`.
|
92
131
|
|
93
|
-
|
94
|
-
|
132
|
+
Returns:
|
133
|
+
Posture task jacobian :math:`J(q)`.
|
95
134
|
"""
|
96
135
|
if self.target_q is None:
|
97
136
|
raise TargetNotSet(self.__class__.__name__)
|
@@ -0,0 +1,142 @@
|
|
1
|
+
"""Relative frame task implementation."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
import numpy as np
|
8
|
+
import numpy.typing as npt
|
9
|
+
|
10
|
+
from ..configuration import Configuration
|
11
|
+
from ..lie import SE3
|
12
|
+
from .exceptions import TargetNotSet, TaskDefinitionError
|
13
|
+
from .task import Task
|
14
|
+
|
15
|
+
|
16
|
+
class RelativeFrameTask(Task):
|
17
|
+
"""Regulate the pose of a frame relative to another frame.
|
18
|
+
|
19
|
+
Attributes:
|
20
|
+
frame_name: Name of the frame to regulate, typically the name of body, geom
|
21
|
+
or site in the robot model.
|
22
|
+
frame_type: The frame type: `body`, `geom` or `site`.
|
23
|
+
root_name: Name of the frame the task is relative to.
|
24
|
+
root_type: The root frame type: `body`, `geom` or `site`.
|
25
|
+
transform_target_to_root: Target pose in the root frame.
|
26
|
+
"""
|
27
|
+
|
28
|
+
k: int = 6
|
29
|
+
transform_target_to_root: Optional[SE3]
|
30
|
+
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
frame_name: str,
|
34
|
+
frame_type: str,
|
35
|
+
root_name: str,
|
36
|
+
root_type: str,
|
37
|
+
position_cost: npt.ArrayLike,
|
38
|
+
orientation_cost: npt.ArrayLike,
|
39
|
+
gain: float = 1.0,
|
40
|
+
lm_damping: float = 0.0,
|
41
|
+
):
|
42
|
+
super().__init__(cost=np.zeros((self.k,)), gain=gain, lm_damping=lm_damping)
|
43
|
+
self.frame_name = frame_name
|
44
|
+
self.frame_type = frame_type
|
45
|
+
self.root_name = root_name
|
46
|
+
self.root_type = root_type
|
47
|
+
self.position_cost = position_cost
|
48
|
+
self.orientation_cost = orientation_cost
|
49
|
+
self.transform_target_to_root = None
|
50
|
+
|
51
|
+
self.set_position_cost(position_cost)
|
52
|
+
self.set_orientation_cost(orientation_cost)
|
53
|
+
|
54
|
+
def set_position_cost(self, position_cost: npt.ArrayLike) -> None:
|
55
|
+
position_cost = np.atleast_1d(position_cost)
|
56
|
+
if position_cost.ndim != 1 or position_cost.shape[0] not in (1, 3):
|
57
|
+
raise TaskDefinitionError(
|
58
|
+
f"{self.__class__.__name__} position cost should be a vector of shape "
|
59
|
+
"1 (aka identical cost for all coordinates) or (3,) but got "
|
60
|
+
f"{position_cost.shape}"
|
61
|
+
)
|
62
|
+
if not np.all(position_cost >= 0.0):
|
63
|
+
raise TaskDefinitionError(
|
64
|
+
f"{self.__class__.__name__} position cost should be >= 0"
|
65
|
+
)
|
66
|
+
self.cost[:3] = position_cost
|
67
|
+
|
68
|
+
def set_orientation_cost(self, orientation_cost: npt.ArrayLike) -> None:
|
69
|
+
orientation_cost = np.atleast_1d(orientation_cost)
|
70
|
+
if orientation_cost.ndim != 1 or orientation_cost.shape[0] not in (1, 3):
|
71
|
+
raise TaskDefinitionError(
|
72
|
+
f"{self.__class__.__name__} orientation cost should be a vector of "
|
73
|
+
"shape 1 (aka identical cost for all coordinates) or (3,) but got "
|
74
|
+
f"{orientation_cost.shape}"
|
75
|
+
)
|
76
|
+
if not np.all(orientation_cost >= 0.0):
|
77
|
+
raise TaskDefinitionError(
|
78
|
+
f"{self.__class__.__name__} position cost should be >= 0"
|
79
|
+
)
|
80
|
+
self.cost[3:] = orientation_cost
|
81
|
+
|
82
|
+
def set_target(self, transform_target_to_root: SE3) -> None:
|
83
|
+
"""Set the target pose in the root frame.
|
84
|
+
|
85
|
+
Args:
|
86
|
+
transform_target_to_root: Transform from the task target frame to the
|
87
|
+
root frame.
|
88
|
+
"""
|
89
|
+
self.transform_target_to_root = transform_target_to_root.copy()
|
90
|
+
|
91
|
+
def set_target_from_configuration(self, configuration: Configuration) -> None:
|
92
|
+
"""Set the target pose from a given robot configuration.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
configuration: Robot configuration :math:`q`.
|
96
|
+
"""
|
97
|
+
self.set_target(
|
98
|
+
configuration.get_transform(
|
99
|
+
self.frame_name,
|
100
|
+
self.frame_type,
|
101
|
+
self.root_name,
|
102
|
+
self.root_type,
|
103
|
+
)
|
104
|
+
)
|
105
|
+
|
106
|
+
def compute_error(self, configuration: Configuration) -> np.ndarray:
|
107
|
+
if self.transform_target_to_root is None:
|
108
|
+
raise TargetNotSet(self.__class__.__name__)
|
109
|
+
|
110
|
+
transform_frame_to_root = configuration.get_transform(
|
111
|
+
self.frame_name,
|
112
|
+
self.frame_type,
|
113
|
+
self.root_name,
|
114
|
+
self.root_type,
|
115
|
+
)
|
116
|
+
return transform_frame_to_root.rminus(self.transform_target_to_root)
|
117
|
+
|
118
|
+
def compute_jacobian(self, configuration: Configuration) -> np.ndarray:
|
119
|
+
if self.transform_target_to_root is None:
|
120
|
+
raise TargetNotSet(self.__class__.__name__)
|
121
|
+
|
122
|
+
jacobian_frame_in_frame = configuration.get_frame_jacobian(
|
123
|
+
self.frame_name, self.frame_type
|
124
|
+
)
|
125
|
+
jacobian_root_in_root = configuration.get_frame_jacobian(
|
126
|
+
self.root_name, self.root_type
|
127
|
+
)
|
128
|
+
|
129
|
+
transform_frame_to_root = configuration.get_transform(
|
130
|
+
self.frame_name,
|
131
|
+
self.frame_type,
|
132
|
+
self.root_name,
|
133
|
+
self.root_type,
|
134
|
+
)
|
135
|
+
transform_frame_to_target = (
|
136
|
+
self.transform_target_to_root.inverse() @ transform_frame_to_root
|
137
|
+
)
|
138
|
+
|
139
|
+
return transform_frame_to_target.jlog() @ (
|
140
|
+
jacobian_frame_in_frame
|
141
|
+
- transform_frame_to_root.inverse().adjoint() @ jacobian_root_in_root
|
142
|
+
)
|
mink/tasks/task.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
"""All kinematic tasks derive from the :class:`Task` base class."""
|
2
|
+
|
1
3
|
import abc
|
2
4
|
from typing import NamedTuple
|
3
5
|
|
@@ -8,10 +10,12 @@ from .exceptions import InvalidDamping, InvalidGain
|
|
8
10
|
|
9
11
|
|
10
12
|
class Objective(NamedTuple):
|
11
|
-
"""Quadratic objective
|
13
|
+
r"""Quadratic objective of the form :math:`\frac{1}{2} x^T H x + c^T x`."""
|
12
14
|
|
13
|
-
H: np.ndarray
|
14
|
-
|
15
|
+
H: np.ndarray
|
16
|
+
"""Hessian matrix, of shape (n_v, n_v)"""
|
17
|
+
c: np.ndarray
|
18
|
+
"""Linear vector, of shape (n_v,)."""
|
15
19
|
|
16
20
|
def value(self, x: np.ndarray) -> float:
|
17
21
|
"""Returns the value of the objective at the input vector."""
|
@@ -19,7 +23,33 @@ class Objective(NamedTuple):
|
|
19
23
|
|
20
24
|
|
21
25
|
class Task(abc.ABC):
|
22
|
-
"""Abstract base class for kinematic tasks.
|
26
|
+
r"""Abstract base class for kinematic tasks.
|
27
|
+
|
28
|
+
Subclasses must implement the configuration-dependent task error
|
29
|
+
:py:meth:`~Task.compute_error` and Jacobian :py:meth:`~Task.compute_jacobian`
|
30
|
+
functions.
|
31
|
+
|
32
|
+
The error function :math:`e(q) \in \mathbb{R}^{k}` is the quantity that
|
33
|
+
the task aims to drive to zero (:math:`k` is the dimension of the
|
34
|
+
task). It appears in the first-order task dynamics:
|
35
|
+
|
36
|
+
.. math::
|
37
|
+
|
38
|
+
J(q) \Delta q = -\alpha e(q)
|
39
|
+
|
40
|
+
The Jacobian matrix :math:`J(q) \in \mathbb{R}^{k \times n_v}`, with
|
41
|
+
:math:`n_v` the dimension of the robot's tangent space, is the
|
42
|
+
derivative of the task error :math:`e(q)` with respect to the
|
43
|
+
configuration :math:`q \in \mathbb{R}^{n_q}`. The configuration displacement
|
44
|
+
:math:`\Delta q` is the output of inverse kinematics; we divide it by dt to get a
|
45
|
+
commanded velocity.
|
46
|
+
|
47
|
+
In the first-order task dynamics, the error :math:`e(q)` is multiplied
|
48
|
+
by the task gain :math:`\alpha \in [0, 1]`. This gain can be 1.0 for
|
49
|
+
dead-beat control (*i.e.* converge as fast as possible), but might be
|
50
|
+
unstable as it neglects our first-order approximation. Lower values
|
51
|
+
cause slow down the task, similar to low-pass filtering.
|
52
|
+
"""
|
23
53
|
|
24
54
|
def __init__(
|
25
55
|
self,
|
@@ -41,7 +71,7 @@ class Task(abc.ABC):
|
|
41
71
|
if not 0.0 <= gain <= 1.0:
|
42
72
|
raise InvalidGain("`gain` must be in the range [0, 1]")
|
43
73
|
|
44
|
-
if
|
74
|
+
if lm_damping < 0.0:
|
45
75
|
raise InvalidDamping("`lm_damping` must be >= 0")
|
46
76
|
|
47
77
|
self.cost = cost
|
@@ -50,23 +80,13 @@ class Task(abc.ABC):
|
|
50
80
|
|
51
81
|
@abc.abstractmethod
|
52
82
|
def compute_error(self, configuration: Configuration) -> np.ndarray:
|
53
|
-
"""Compute the task error
|
54
|
-
|
55
|
-
The error function e(q), of shape (k,), is the quantity that the task aims to
|
56
|
-
drive to zero. It appears in the first-order task dynamics:
|
57
|
-
|
58
|
-
J(q) Δq = -alpha e(q)
|
83
|
+
"""Compute the task error at the current configuration.
|
59
84
|
|
60
|
-
|
61
|
-
|
62
|
-
`compute_jacobian`. Finally, the configuration displacement Δq is the output
|
63
|
-
of inverse kinematics.
|
85
|
+
Args:
|
86
|
+
configuration: Robot configuration :math:`q`.
|
64
87
|
|
65
|
-
|
66
|
-
|
67
|
-
(i.e., converge as fast as possible), but might be unstable as it neglects the
|
68
|
-
first-order approximation. Lower values make the task slower and have a similar
|
69
|
-
effect to low-pass filtering.
|
88
|
+
Returns:
|
89
|
+
Task error vector :math:`e(q)`.
|
70
90
|
"""
|
71
91
|
raise NotImplementedError
|
72
92
|
|
@@ -74,20 +94,33 @@ class Task(abc.ABC):
|
|
74
94
|
def compute_jacobian(self, configuration: Configuration) -> np.ndarray:
|
75
95
|
"""Compute the task Jacobian at the current configuration.
|
76
96
|
|
77
|
-
|
78
|
-
|
79
|
-
|
97
|
+
Args:
|
98
|
+
configuration: Robot configuration :math:`q`.
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
Task jacobian :math:`J(q)`.
|
80
102
|
"""
|
81
103
|
raise NotImplementedError
|
82
104
|
|
83
105
|
def compute_qp_objective(self, configuration: Configuration) -> Objective:
|
84
|
-
"""Compute the matrix-vector pair :math:`(H, c)` of the QP objective.
|
106
|
+
r"""Compute the matrix-vector pair :math:`(H, c)` of the QP objective.
|
107
|
+
|
108
|
+
This pair is such that the contribution of the task to the QP objective is:
|
85
109
|
|
86
|
-
|
87
|
-
|
110
|
+
.. math::
|
111
|
+
|
112
|
+
\| J \Delta q + \alpha e \|_{W}^2 = \frac{1}{2} \Delta q^T H
|
113
|
+
\Delta q + c^T q
|
114
|
+
|
115
|
+
The weight matrix :math:`W \in \mathbb{R}^{k \times k}` weights and
|
116
|
+
normalizes task coordinates to the same unit. The unit of the overall
|
117
|
+
contribution is [cost]^2.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
configuration: Robot configuration :math:`q`.
|
88
121
|
|
89
|
-
|
90
|
-
|
122
|
+
Returns:
|
123
|
+
Pair :math:`(H(q), c(q))`.
|
91
124
|
"""
|
92
125
|
jacobian = self.compute_jacobian(configuration) # (k, nv)
|
93
126
|
minus_gain_error = -self.gain * self.compute_error(configuration) # (k,)
|
mink/utils.py
CHANGED
@@ -17,8 +17,8 @@ def move_mocap_to_frame(
|
|
17
17
|
"""Initialize mocap body pose at a desired frame.
|
18
18
|
|
19
19
|
Args:
|
20
|
-
model:
|
21
|
-
data:
|
20
|
+
model: Mujoco model.
|
21
|
+
data: Mujoco data.
|
22
22
|
mocap_name: The name of the mocap body.
|
23
23
|
frame_name: The desired frame name.
|
24
24
|
frame_type: The desired frame type. Can be "body", "geom" or "site".
|
@@ -36,7 +36,15 @@ def move_mocap_to_frame(
|
|
36
36
|
|
37
37
|
|
38
38
|
def get_freejoint_dims(model: mujoco.MjModel) -> tuple[list[int], list[int]]:
|
39
|
-
"""Get all floating joint configuration and tangent indices.
|
39
|
+
"""Get all floating joint configuration and tangent indices.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
model: Mujoco model.
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
A (q_ids, v_ids) pair containing all floating joint indices in the
|
46
|
+
configuration and tangent spaces respectively.
|
47
|
+
"""
|
40
48
|
q_ids: list[int] = []
|
41
49
|
v_ids: list[int] = []
|
42
50
|
for j in range(model.njnt):
|
@@ -56,9 +64,9 @@ def custom_configuration_vector(
|
|
56
64
|
"""Generate a configuration vector where named joints have specific values.
|
57
65
|
|
58
66
|
Args:
|
59
|
-
model:
|
67
|
+
model: Mujoco model.
|
60
68
|
key_name: Optional keyframe name to initialize the configuration vector from.
|
61
|
-
Otherwise, the default pose qpos0 is used.
|
69
|
+
Otherwise, the default pose `qpos0` is used.
|
62
70
|
kwargs: Custom values for joint coordinates.
|
63
71
|
|
64
72
|
Returns:
|
@@ -93,7 +101,7 @@ def get_subtree_geom_ids(model: mujoco.MjModel, body_id: int) -> list[int]:
|
|
93
101
|
"""Get all geoms belonging to subtree starting at a given body.
|
94
102
|
|
95
103
|
Args:
|
96
|
-
model:
|
104
|
+
model: Mujoco model.
|
97
105
|
body_id: ID of body where subtree starts.
|
98
106
|
|
99
107
|
Returns:
|
@@ -117,7 +125,7 @@ def get_body_geom_ids(model: mujoco.MjModel, body_id: int) -> list[int]:
|
|
117
125
|
"""Get all geoms belonging to a given body.
|
118
126
|
|
119
127
|
Args:
|
120
|
-
model:
|
128
|
+
model: Mujoco model.
|
121
129
|
body_id: ID of body.
|
122
130
|
|
123
131
|
Returns:
|