openscvx 0.3.2.dev170__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.
Potentially problematic release.
This version of openscvx might be problematic. Click here for more details.
- openscvx/__init__.py +123 -0
- openscvx/_version.py +34 -0
- openscvx/algorithms/__init__.py +92 -0
- openscvx/algorithms/autotuning.py +24 -0
- openscvx/algorithms/base.py +351 -0
- openscvx/algorithms/optimization_results.py +215 -0
- openscvx/algorithms/penalized_trust_region.py +384 -0
- openscvx/config.py +437 -0
- openscvx/discretization/__init__.py +47 -0
- openscvx/discretization/discretization.py +236 -0
- openscvx/expert/__init__.py +23 -0
- openscvx/expert/byof.py +326 -0
- openscvx/expert/lowering.py +419 -0
- openscvx/expert/validation.py +357 -0
- openscvx/integrators/__init__.py +48 -0
- openscvx/integrators/runge_kutta.py +281 -0
- openscvx/lowered/__init__.py +30 -0
- openscvx/lowered/cvxpy_constraints.py +23 -0
- openscvx/lowered/cvxpy_variables.py +124 -0
- openscvx/lowered/dynamics.py +34 -0
- openscvx/lowered/jax_constraints.py +133 -0
- openscvx/lowered/parameters.py +54 -0
- openscvx/lowered/problem.py +70 -0
- openscvx/lowered/unified.py +718 -0
- openscvx/plotting/__init__.py +63 -0
- openscvx/plotting/plotting.py +756 -0
- openscvx/plotting/scp_iteration.py +299 -0
- openscvx/plotting/viser/__init__.py +126 -0
- openscvx/plotting/viser/animated.py +605 -0
- openscvx/plotting/viser/plotly_integration.py +333 -0
- openscvx/plotting/viser/primitives.py +355 -0
- openscvx/plotting/viser/scp.py +459 -0
- openscvx/plotting/viser/server.py +112 -0
- openscvx/problem.py +734 -0
- openscvx/propagation/__init__.py +60 -0
- openscvx/propagation/post_processing.py +104 -0
- openscvx/propagation/propagation.py +248 -0
- openscvx/solvers/__init__.py +51 -0
- openscvx/solvers/cvxpy.py +226 -0
- openscvx/symbolic/__init__.py +9 -0
- openscvx/symbolic/augmentation.py +630 -0
- openscvx/symbolic/builder.py +492 -0
- openscvx/symbolic/constraint_set.py +92 -0
- openscvx/symbolic/expr/__init__.py +222 -0
- openscvx/symbolic/expr/arithmetic.py +517 -0
- openscvx/symbolic/expr/array.py +632 -0
- openscvx/symbolic/expr/constraint.py +796 -0
- openscvx/symbolic/expr/control.py +135 -0
- openscvx/symbolic/expr/expr.py +720 -0
- openscvx/symbolic/expr/lie/__init__.py +87 -0
- openscvx/symbolic/expr/lie/adjoint.py +357 -0
- openscvx/symbolic/expr/lie/se3.py +172 -0
- openscvx/symbolic/expr/lie/so3.py +138 -0
- openscvx/symbolic/expr/linalg.py +279 -0
- openscvx/symbolic/expr/math.py +699 -0
- openscvx/symbolic/expr/spatial.py +209 -0
- openscvx/symbolic/expr/state.py +607 -0
- openscvx/symbolic/expr/stl.py +136 -0
- openscvx/symbolic/expr/variable.py +321 -0
- openscvx/symbolic/hashing.py +112 -0
- openscvx/symbolic/lower.py +760 -0
- openscvx/symbolic/lowerers/__init__.py +106 -0
- openscvx/symbolic/lowerers/cvxpy.py +1302 -0
- openscvx/symbolic/lowerers/jax.py +1382 -0
- openscvx/symbolic/preprocessing.py +757 -0
- openscvx/symbolic/problem.py +110 -0
- openscvx/symbolic/time.py +116 -0
- openscvx/symbolic/unified.py +420 -0
- openscvx/utils/__init__.py +20 -0
- openscvx/utils/cache.py +131 -0
- openscvx/utils/caching.py +210 -0
- openscvx/utils/printing.py +301 -0
- openscvx/utils/profiling.py +37 -0
- openscvx/utils/utils.py +100 -0
- openscvx-0.3.2.dev170.dist-info/METADATA +350 -0
- openscvx-0.3.2.dev170.dist-info/RECORD +79 -0
- openscvx-0.3.2.dev170.dist-info/WHEEL +5 -0
- openscvx-0.3.2.dev170.dist-info/licenses/LICENSE +201 -0
- openscvx-0.3.2.dev170.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Lie algebra operations for rigid body dynamics.
|
|
2
|
+
|
|
3
|
+
This module provides symbolic expression nodes for Lie algebra operations
|
|
4
|
+
commonly used in 6-DOF rigid body dynamics, robotics, and geometric mechanics.
|
|
5
|
+
These operations enable elegant formulations of Newton-Euler dynamics using
|
|
6
|
+
spatial vectors (twists and wrenches).
|
|
7
|
+
|
|
8
|
+
The module provides two tiers of functionality:
|
|
9
|
+
|
|
10
|
+
**Built-in operators** work out of the box and include adjoint/coadjoint
|
|
11
|
+
operators for dynamics (_e.g._ ``Adjoint``, ``AdjointDual``) and frame transformations
|
|
12
|
+
(_e.g._ ``SE3Adjoint``, ``SE3AdjointDual``).
|
|
13
|
+
|
|
14
|
+
**jaxlie-backed operators** require ``pip install openscvx[lie]`` and provide
|
|
15
|
+
exponential/logarithm maps for SO(3) and SE(3) groups (_e.g._ ``SO3Exp``, ``SO3Log``,
|
|
16
|
+
``SE3Exp``, ``SE3Log``).
|
|
17
|
+
|
|
18
|
+
Conventions:
|
|
19
|
+
- Twist (spatial velocity): ξ = [v; ω] where v ∈ ℝ³ is linear velocity
|
|
20
|
+
and ω ∈ ℝ³ is angular velocity (both in body frame)
|
|
21
|
+
- Wrench (spatial force): F = [f; τ] where f ∈ ℝ³ is force and τ ∈ ℝ³
|
|
22
|
+
is torque (both in body frame)
|
|
23
|
+
|
|
24
|
+
Note:
|
|
25
|
+
The twist convention [v; ω] (linear first, angular second) matches jaxlie's
|
|
26
|
+
SE3 tangent parameterization, so no reordering is needed during lowering.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
Newton-Euler dynamics for a rigid body using the coadjoint operator::
|
|
30
|
+
|
|
31
|
+
import openscvx as ox
|
|
32
|
+
|
|
33
|
+
twist = ox.State("twist", shape=(6,))
|
|
34
|
+
M = ox.Parameter("M", shape=(6, 6), value=spatial_inertia)
|
|
35
|
+
wrench = ox.Control("wrench", shape=(6,))
|
|
36
|
+
|
|
37
|
+
momentum = M @ twist
|
|
38
|
+
bias_force = ox.lie.AdjointDual(twist, momentum)
|
|
39
|
+
twist_dot = M_inv @ (wrench - bias_force)
|
|
40
|
+
|
|
41
|
+
Product of Exponentials forward kinematics (requires jaxlie)::
|
|
42
|
+
|
|
43
|
+
screw_axis = ox.Constant(np.array([0, 0, 0, 0, 0, 1]))
|
|
44
|
+
theta = ox.State("theta", shape=(1,))
|
|
45
|
+
T_joint = ox.lie.SE3Exp(screw_axis * theta) # 4×4 matrix
|
|
46
|
+
|
|
47
|
+
References:
|
|
48
|
+
- Murray, Li, Sastry: "A Mathematical Introduction to Robotic Manipulation"
|
|
49
|
+
- Featherstone: "Rigid Body Dynamics Algorithms"
|
|
50
|
+
- Sola et al.: "A micro Lie theory for state estimation in robotics"
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# Core operators - no dependencies
|
|
54
|
+
from .adjoint import Adjoint, AdjointDual, SE3Adjoint, SE3AdjointDual
|
|
55
|
+
|
|
56
|
+
# jaxlie-backed operators - optional dependency
|
|
57
|
+
try:
|
|
58
|
+
from .se3 import SE3Exp, SE3Log
|
|
59
|
+
from .so3 import SO3Exp, SO3Log
|
|
60
|
+
|
|
61
|
+
_JAXLIE_AVAILABLE = True
|
|
62
|
+
except ImportError:
|
|
63
|
+
_JAXLIE_AVAILABLE = False
|
|
64
|
+
|
|
65
|
+
def _make_stub(name: str):
|
|
66
|
+
"""Create a stub class that raises ImportError on instantiation."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, *args, **kwargs):
|
|
69
|
+
raise ImportError(f"{name} requires jaxlie. Install with: pip install openscvx[lie]")
|
|
70
|
+
|
|
71
|
+
return type(name, (), {"__init__": __init__})
|
|
72
|
+
|
|
73
|
+
SO3Exp = _make_stub("SO3Exp")
|
|
74
|
+
SO3Log = _make_stub("SO3Log")
|
|
75
|
+
SE3Exp = _make_stub("SE3Exp")
|
|
76
|
+
SE3Log = _make_stub("SE3Log")
|
|
77
|
+
|
|
78
|
+
__all__ = [
|
|
79
|
+
"AdjointDual",
|
|
80
|
+
"Adjoint",
|
|
81
|
+
"SE3Adjoint",
|
|
82
|
+
"SE3AdjointDual",
|
|
83
|
+
"SO3Exp",
|
|
84
|
+
"SO3Log",
|
|
85
|
+
"SE3Exp",
|
|
86
|
+
"SE3Log",
|
|
87
|
+
]
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""Adjoint and coadjoint operators for rigid body dynamics.
|
|
2
|
+
|
|
3
|
+
This module provides the core Lie algebra operators for 6-DOF rigid body
|
|
4
|
+
dynamics. These operators require no external dependencies and work with
|
|
5
|
+
the standard openscvx installation.
|
|
6
|
+
|
|
7
|
+
The module uses the following conventions:
|
|
8
|
+
- Twist (spatial velocity): ξ = [v; ω] where v ∈ ℝ³ is linear velocity
|
|
9
|
+
and ω ∈ ℝ³ is angular velocity (both in body frame)
|
|
10
|
+
- Wrench (spatial force): F = [f; τ] where f ∈ ℝ³ is force and τ ∈ ℝ³
|
|
11
|
+
is torque (both in body frame)
|
|
12
|
+
- Momentum: μ = [p; L] where p ∈ ℝ³ is linear momentum and L ∈ ℝ³
|
|
13
|
+
is angular momentum
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Tuple
|
|
17
|
+
|
|
18
|
+
from ..expr import Expr, to_expr
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AdjointDual(Expr):
|
|
22
|
+
"""Coadjoint operator ad* for computing Coriolis and centrifugal forces.
|
|
23
|
+
|
|
24
|
+
Computes the coadjoint action ad*_ξ(μ) which represents the rate of change
|
|
25
|
+
of momentum due to body rotation. This is the key term in Newton-Euler
|
|
26
|
+
dynamics that captures Coriolis and centrifugal effects.
|
|
27
|
+
|
|
28
|
+
For se(3), given twist ξ = [v; ω] and momentum μ = [f; τ]:
|
|
29
|
+
|
|
30
|
+
ad*_ξ(μ) = [ ω × f + v × τ ]
|
|
31
|
+
[ ω × τ ]
|
|
32
|
+
|
|
33
|
+
This appears in the Newton-Euler equations as:
|
|
34
|
+
|
|
35
|
+
M @ ξ_dot = F_ext - ad*_ξ(M @ ξ)
|
|
36
|
+
|
|
37
|
+
where M is the spatial inertia matrix and F_ext is the external wrench.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
twist: 6D twist vector [v; ω] (linear velocity, angular velocity)
|
|
41
|
+
momentum: 6D momentum vector [p; L] or [f; τ] (linear, angular)
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
Compute the bias force (Coriolis + centrifugal) for rigid body dynamics::
|
|
45
|
+
|
|
46
|
+
import openscvx as ox
|
|
47
|
+
|
|
48
|
+
twist = ox.State("twist", shape=(6,))
|
|
49
|
+
M = ox.Parameter("M", shape=(6, 6), value=inertia_matrix)
|
|
50
|
+
|
|
51
|
+
momentum = M @ twist
|
|
52
|
+
bias_force = ox.lie.AdjointDual(twist, momentum)
|
|
53
|
+
|
|
54
|
+
# In dynamics: twist_dot = M_inv @ (wrench - bias_force)
|
|
55
|
+
|
|
56
|
+
Note:
|
|
57
|
+
The coadjoint is related to the adjoint by: ad*_ξ = -(ad_ξ)^T
|
|
58
|
+
|
|
59
|
+
For the special case of pure rotation (v=0) with diagonal inertia,
|
|
60
|
+
the angular part reduces to the familiar ω × (J @ ω) term.
|
|
61
|
+
|
|
62
|
+
See Also:
|
|
63
|
+
Adjoint: The adjoint operator for twist-on-twist action
|
|
64
|
+
SSM: 3x3 skew-symmetric matrix for cross products
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, twist, momentum):
|
|
68
|
+
"""Initialize a coadjoint operator.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
twist: 6D twist vector [v; ω] with shape (6,)
|
|
72
|
+
momentum: 6D momentum vector [p; L] with shape (6,)
|
|
73
|
+
"""
|
|
74
|
+
self.twist = to_expr(twist)
|
|
75
|
+
self.momentum = to_expr(momentum)
|
|
76
|
+
|
|
77
|
+
def children(self):
|
|
78
|
+
return [self.twist, self.momentum]
|
|
79
|
+
|
|
80
|
+
def canonicalize(self) -> "Expr":
|
|
81
|
+
twist = self.twist.canonicalize()
|
|
82
|
+
momentum = self.momentum.canonicalize()
|
|
83
|
+
return AdjointDual(twist, momentum)
|
|
84
|
+
|
|
85
|
+
def check_shape(self) -> Tuple[int, ...]:
|
|
86
|
+
"""Check that inputs are 6D vectors and return output shape.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
tuple: Shape (6,) for the resulting coadjoint vector
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
ValueError: If twist or momentum do not have shape (6,)
|
|
93
|
+
"""
|
|
94
|
+
twist_shape = self.twist.check_shape()
|
|
95
|
+
momentum_shape = self.momentum.check_shape()
|
|
96
|
+
|
|
97
|
+
if twist_shape != (6,):
|
|
98
|
+
raise ValueError(f"AdjointDual expects twist with shape (6,), got {twist_shape}")
|
|
99
|
+
if momentum_shape != (6,):
|
|
100
|
+
raise ValueError(f"AdjointDual expects momentum with shape (6,), got {momentum_shape}")
|
|
101
|
+
|
|
102
|
+
return (6,)
|
|
103
|
+
|
|
104
|
+
def __repr__(self):
|
|
105
|
+
return f"ad_dual({self.twist!r}, {self.momentum!r})"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Adjoint(Expr):
|
|
109
|
+
"""Adjoint operator ad (Lie bracket) for twist-on-twist action.
|
|
110
|
+
|
|
111
|
+
Computes the adjoint action ad_ξ₁(ξ₂) which represents the Lie bracket
|
|
112
|
+
[ξ₁, ξ₂] of two twists. This is used for velocity propagation in
|
|
113
|
+
kinematic chains and acceleration computations.
|
|
114
|
+
|
|
115
|
+
For se(3), given twists ξ₁ = [v₁; ω₁] and ξ₂ = [v₂; ω₂]:
|
|
116
|
+
|
|
117
|
+
ad_ξ₁(ξ₂) = [ξ₁, ξ₂] = [ ω₁ × v₂ - ω₂ × v₁ ]
|
|
118
|
+
[ ω₁ × ω₂ ]
|
|
119
|
+
|
|
120
|
+
Equivalently using the adjoint matrix:
|
|
121
|
+
|
|
122
|
+
ad_ξ = [ [ω]× 0 ]
|
|
123
|
+
[ [v]× [ω]× ]
|
|
124
|
+
|
|
125
|
+
where [·]× denotes the 3x3 skew-symmetric (cross product) matrix.
|
|
126
|
+
|
|
127
|
+
Attributes:
|
|
128
|
+
twist1: First 6D twist vector [v₁; ω₁]
|
|
129
|
+
twist2: Second 6D twist vector [v₂; ω₂]
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
Compute the Lie bracket of two twists::
|
|
133
|
+
|
|
134
|
+
import openscvx as ox
|
|
135
|
+
|
|
136
|
+
twist1 = ox.State("twist1", shape=(6,))
|
|
137
|
+
twist2 = ox.State("twist2", shape=(6,))
|
|
138
|
+
|
|
139
|
+
bracket = ox.lie.Adjoint(twist1, twist2)
|
|
140
|
+
|
|
141
|
+
Velocity propagation in a kinematic chain::
|
|
142
|
+
|
|
143
|
+
# Child link velocity includes parent velocity plus relative motion
|
|
144
|
+
# V_child = Ad_T @ V_parent + joint_twist * q_dot
|
|
145
|
+
|
|
146
|
+
Note:
|
|
147
|
+
The adjoint satisfies the Jacobi identity and is antisymmetric:
|
|
148
|
+
ad_ξ₁(ξ₂) = -ad_ξ₂(ξ₁)
|
|
149
|
+
|
|
150
|
+
See Also:
|
|
151
|
+
AdjointDual: The coadjoint operator for momentum dynamics
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
def __init__(self, twist1, twist2):
|
|
155
|
+
"""Initialize an adjoint operator.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
twist1: First 6D twist vector [v; ω] with shape (6,)
|
|
159
|
+
twist2: Second 6D twist vector [v; ω] with shape (6,)
|
|
160
|
+
"""
|
|
161
|
+
self.twist1 = to_expr(twist1)
|
|
162
|
+
self.twist2 = to_expr(twist2)
|
|
163
|
+
|
|
164
|
+
def children(self):
|
|
165
|
+
return [self.twist1, self.twist2]
|
|
166
|
+
|
|
167
|
+
def canonicalize(self) -> "Expr":
|
|
168
|
+
twist1 = self.twist1.canonicalize()
|
|
169
|
+
twist2 = self.twist2.canonicalize()
|
|
170
|
+
return Adjoint(twist1, twist2)
|
|
171
|
+
|
|
172
|
+
def check_shape(self) -> Tuple[int, ...]:
|
|
173
|
+
"""Check that inputs are 6D vectors and return output shape.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
tuple: Shape (6,) for the resulting Lie bracket
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
ValueError: If either twist does not have shape (6,)
|
|
180
|
+
"""
|
|
181
|
+
twist1_shape = self.twist1.check_shape()
|
|
182
|
+
twist2_shape = self.twist2.check_shape()
|
|
183
|
+
|
|
184
|
+
if twist1_shape != (6,):
|
|
185
|
+
raise ValueError(f"Adjoint expects twist1 with shape (6,), got {twist1_shape}")
|
|
186
|
+
if twist2_shape != (6,):
|
|
187
|
+
raise ValueError(f"Adjoint expects twist2 with shape (6,), got {twist2_shape}")
|
|
188
|
+
|
|
189
|
+
return (6,)
|
|
190
|
+
|
|
191
|
+
def __repr__(self):
|
|
192
|
+
return f"ad({self.twist1!r}, {self.twist2!r})"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class SE3Adjoint(Expr):
|
|
196
|
+
"""SE(3) Adjoint representation Ad_T for transforming twists between frames.
|
|
197
|
+
|
|
198
|
+
Computes the 6×6 adjoint matrix Ad_T that transforms twists from one
|
|
199
|
+
coordinate frame to another. Given a transformation T_ab from frame A to
|
|
200
|
+
frame B, the adjoint transforms a twist expressed in frame A to frame B:
|
|
201
|
+
|
|
202
|
+
ξ_b = Ad_{T_ab} @ ξ_a
|
|
203
|
+
|
|
204
|
+
For SE(3), given T with rotation R and translation p:
|
|
205
|
+
|
|
206
|
+
Ad_T = [ R 0 ]
|
|
207
|
+
[ [p]×R R ]
|
|
208
|
+
|
|
209
|
+
where [p]× is the 3×3 skew-symmetric matrix of p.
|
|
210
|
+
|
|
211
|
+
This is essential for:
|
|
212
|
+
|
|
213
|
+
- Velocity propagation through kinematic chains
|
|
214
|
+
- Computing geometric Jacobians for manipulators
|
|
215
|
+
- Recursive Newton-Euler dynamics algorithms
|
|
216
|
+
|
|
217
|
+
Attributes:
|
|
218
|
+
transform: 4×4 homogeneous transformation matrix
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
Transform a body twist to the world frame::
|
|
222
|
+
|
|
223
|
+
import openscvx as ox
|
|
224
|
+
|
|
225
|
+
T_world_body = forward_kinematics(q) # 4×4 transform
|
|
226
|
+
twist_body = ox.State("twist_body", shape=(6,))
|
|
227
|
+
|
|
228
|
+
# Transform twist to world frame
|
|
229
|
+
Ad_T = ox.lie.SE3Adjoint(T_world_body) # 6×6 matrix
|
|
230
|
+
twist_world = Ad_T @ twist_body
|
|
231
|
+
|
|
232
|
+
Compute geometric Jacobian columns::
|
|
233
|
+
|
|
234
|
+
# Each column of the geometric Jacobian is Ad_{T_0i} @ ξ_i
|
|
235
|
+
J_col_i = ox.lie.SE3Adjoint(T_0_to_i) @ screw_axis_i
|
|
236
|
+
|
|
237
|
+
Note:
|
|
238
|
+
The adjoint satisfies: Ad_{T1 @ T2} = Ad_{T1} @ Ad_{T2}
|
|
239
|
+
|
|
240
|
+
See Also:
|
|
241
|
+
- SE3AdjointDual: For transforming wrenches between frames
|
|
242
|
+
- Adjoint: The small adjoint (Lie bracket) for twist-on-twist action
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
def __init__(self, transform):
|
|
246
|
+
"""Initialize SE3 Adjoint operator.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
transform: 4×4 homogeneous transformation matrix with shape (4, 4)
|
|
250
|
+
"""
|
|
251
|
+
self.transform = to_expr(transform)
|
|
252
|
+
|
|
253
|
+
def children(self):
|
|
254
|
+
return [self.transform]
|
|
255
|
+
|
|
256
|
+
def canonicalize(self) -> "Expr":
|
|
257
|
+
transform = self.transform.canonicalize()
|
|
258
|
+
return SE3Adjoint(transform)
|
|
259
|
+
|
|
260
|
+
def check_shape(self) -> Tuple[int, ...]:
|
|
261
|
+
"""Check that input is a 4×4 matrix and return output shape.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
tuple: Shape (6, 6) for the adjoint matrix
|
|
265
|
+
|
|
266
|
+
Raises:
|
|
267
|
+
ValueError: If transform does not have shape (4, 4)
|
|
268
|
+
"""
|
|
269
|
+
transform_shape = self.transform.check_shape()
|
|
270
|
+
if transform_shape != (4, 4):
|
|
271
|
+
raise ValueError(
|
|
272
|
+
f"SE3Adjoint expects transform with shape (4, 4), got {transform_shape}"
|
|
273
|
+
)
|
|
274
|
+
return (6, 6)
|
|
275
|
+
|
|
276
|
+
def __repr__(self):
|
|
277
|
+
return f"Ad({self.transform!r})"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class SE3AdjointDual(Expr):
|
|
281
|
+
"""SE(3) coadjoint representation Ad*_T for transforming wrenches between frames.
|
|
282
|
+
|
|
283
|
+
Computes the 6×6 coadjoint matrix Ad*_T that transforms wrenches from one
|
|
284
|
+
coordinate frame to another. Given a transformation T_ab from frame A to
|
|
285
|
+
frame B, the coadjoint transforms a wrench expressed in frame B to frame A:
|
|
286
|
+
|
|
287
|
+
F_a = Ad*_{T_ab} @ F_b
|
|
288
|
+
|
|
289
|
+
For SE(3), given T with rotation R and translation p:
|
|
290
|
+
|
|
291
|
+
Ad*_T = [ R [p]×R ]
|
|
292
|
+
[ 0 R ]
|
|
293
|
+
|
|
294
|
+
This is the transpose-inverse of Ad_T: Ad*_T = (Ad_T)^{-T}
|
|
295
|
+
|
|
296
|
+
This is essential for:
|
|
297
|
+
|
|
298
|
+
- Force/torque propagation in dynamics
|
|
299
|
+
- Transforming wrenches between end-effector and base frames
|
|
300
|
+
- Recursive Newton-Euler dynamics algorithms
|
|
301
|
+
|
|
302
|
+
Attributes:
|
|
303
|
+
transform: 4×4 homogeneous transformation matrix
|
|
304
|
+
|
|
305
|
+
Example:
|
|
306
|
+
Transform a wrench from end-effector to base frame::
|
|
307
|
+
|
|
308
|
+
import openscvx as ox
|
|
309
|
+
|
|
310
|
+
T_base_ee = forward_kinematics(q) # 4×4 transform
|
|
311
|
+
wrench_ee = ox.Control("wrench_ee", shape=(6,))
|
|
312
|
+
|
|
313
|
+
# Transform wrench to base frame
|
|
314
|
+
Ad_star_T = ox.lie.SE3AdjointDual(T_base_ee) # 6×6 matrix
|
|
315
|
+
wrench_base = Ad_star_T @ wrench_ee
|
|
316
|
+
|
|
317
|
+
Note:
|
|
318
|
+
The coadjoint is related to the adjoint by: Ad*_T = (Ad_T)^{-T}
|
|
319
|
+
|
|
320
|
+
See Also:
|
|
321
|
+
- SE3Adjoint: For transforming twists between frames
|
|
322
|
+
- AdjointDual: The small coadjoint for Coriolis/centrifugal forces
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
def __init__(self, transform):
|
|
326
|
+
"""Initialize SE3 coadjoint operator.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
transform: 4×4 homogeneous transformation matrix with shape (4, 4)
|
|
330
|
+
"""
|
|
331
|
+
self.transform = to_expr(transform)
|
|
332
|
+
|
|
333
|
+
def children(self):
|
|
334
|
+
return [self.transform]
|
|
335
|
+
|
|
336
|
+
def canonicalize(self) -> "Expr":
|
|
337
|
+
transform = self.transform.canonicalize()
|
|
338
|
+
return SE3AdjointDual(transform)
|
|
339
|
+
|
|
340
|
+
def check_shape(self) -> Tuple[int, ...]:
|
|
341
|
+
"""Check that input is a 4×4 matrix and return output shape.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
tuple: Shape (6, 6) for the coadjoint matrix
|
|
345
|
+
|
|
346
|
+
Raises:
|
|
347
|
+
ValueError: If transform does not have shape (4, 4)
|
|
348
|
+
"""
|
|
349
|
+
transform_shape = self.transform.check_shape()
|
|
350
|
+
if transform_shape != (4, 4):
|
|
351
|
+
raise ValueError(
|
|
352
|
+
f"SE3AdjointDual expects transform with shape (4, 4), got {transform_shape}"
|
|
353
|
+
)
|
|
354
|
+
return (6, 6)
|
|
355
|
+
|
|
356
|
+
def __repr__(self):
|
|
357
|
+
return f"Ad_dual({self.transform!r})"
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""SE(3) Lie group operations for rigid body transformations.
|
|
2
|
+
|
|
3
|
+
This module provides exponential and logarithm maps for the SE(3) rigid
|
|
4
|
+
transformation group, enabling twist to transformation matrix conversions
|
|
5
|
+
and vice versa. These are essential for Product of Exponentials (PoE)
|
|
6
|
+
forward kinematics in robotic manipulators.
|
|
7
|
+
|
|
8
|
+
Requires jaxlie: pip install openscvx[lie]
|
|
9
|
+
|
|
10
|
+
Note:
|
|
11
|
+
The twist convention [v; ω] (linear first, angular second) matches jaxlie's
|
|
12
|
+
SE3 tangent parameterization, so no reordering is needed during lowering.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
|
|
17
|
+
import jaxlie # noqa: F401 - validates jaxlie is installed
|
|
18
|
+
|
|
19
|
+
from ..expr import Expr, to_expr
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SE3Exp(Expr):
|
|
23
|
+
"""Exponential map from se(3) twist to SE(3) transformation matrix.
|
|
24
|
+
|
|
25
|
+
Maps a 6D twist vector to a 4×4 homogeneous transformation matrix.
|
|
26
|
+
Uses jaxlie for numerically robust implementation with proper handling
|
|
27
|
+
of small angles and translations.
|
|
28
|
+
|
|
29
|
+
The twist ξ = [v; ω] follows the convention:
|
|
30
|
+
|
|
31
|
+
- v: 3D linear velocity component
|
|
32
|
+
- ω: 3D angular velocity component
|
|
33
|
+
|
|
34
|
+
This is the key operation for Product of Exponentials (PoE) forward
|
|
35
|
+
kinematics in robotic manipulators.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
twist: 6D twist vector [v; ω] with shape (6,)
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
Product of Exponentials forward kinematics::
|
|
42
|
+
|
|
43
|
+
import openscvx as ox
|
|
44
|
+
import numpy as np
|
|
45
|
+
|
|
46
|
+
# Screw axis for revolute joint about z-axis at origin
|
|
47
|
+
screw_axis = np.array([0, 0, 0, 0, 0, 1]) # [v; ω]
|
|
48
|
+
theta = ox.State("theta", shape=(1,))
|
|
49
|
+
|
|
50
|
+
# Joint transformation
|
|
51
|
+
T = ox.lie.SE3Exp(ox.Constant(screw_axis) * theta) # 4×4 matrix
|
|
52
|
+
|
|
53
|
+
# Chain multiple joints
|
|
54
|
+
T_01 = ox.lie.SE3Exp(screw1 * q1)
|
|
55
|
+
T_12 = ox.lie.SE3Exp(screw2 * q2)
|
|
56
|
+
T_02 = T_01 @ T_12
|
|
57
|
+
|
|
58
|
+
Extract position from transformation::
|
|
59
|
+
|
|
60
|
+
T_ee = forward_kinematics(joint_angles)
|
|
61
|
+
p_ee = T_ee[:3, 3] # End-effector position
|
|
62
|
+
|
|
63
|
+
Note:
|
|
64
|
+
The twist convention [v; ω] matches jaxlie's SE3 tangent
|
|
65
|
+
parameterization, so no reordering is performed.
|
|
66
|
+
|
|
67
|
+
See Also:
|
|
68
|
+
- SE3Log: Inverse operation (transformation matrix to twist)
|
|
69
|
+
- SO3Exp: Rotation-only exponential map
|
|
70
|
+
- AdjointDual: For dynamics computations with twists
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, twist):
|
|
74
|
+
"""Initialize SE3 exponential map.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
twist: 6D twist vector [v; ω] with shape (6,)
|
|
78
|
+
"""
|
|
79
|
+
self.twist = to_expr(twist)
|
|
80
|
+
|
|
81
|
+
def children(self):
|
|
82
|
+
return [self.twist]
|
|
83
|
+
|
|
84
|
+
def canonicalize(self) -> "Expr":
|
|
85
|
+
twist = self.twist.canonicalize()
|
|
86
|
+
return SE3Exp(twist)
|
|
87
|
+
|
|
88
|
+
def check_shape(self) -> Tuple[int, ...]:
|
|
89
|
+
"""Check that input is a 6D vector and return output shape.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
tuple: Shape (4, 4) for the homogeneous transformation matrix
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
ValueError: If twist does not have shape (6,)
|
|
96
|
+
"""
|
|
97
|
+
twist_shape = self.twist.check_shape()
|
|
98
|
+
if twist_shape != (6,):
|
|
99
|
+
raise ValueError(f"SE3Exp expects twist with shape (6,), got {twist_shape}")
|
|
100
|
+
return (4, 4)
|
|
101
|
+
|
|
102
|
+
def __repr__(self):
|
|
103
|
+
return f"SE3Exp({self.twist!r})"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class SE3Log(Expr):
|
|
107
|
+
"""Logarithm map from SE(3) transformation matrix to se(3) twist.
|
|
108
|
+
|
|
109
|
+
Maps a 4×4 homogeneous transformation matrix to a 6D twist vector.
|
|
110
|
+
Uses jaxlie for numerically robust implementation.
|
|
111
|
+
|
|
112
|
+
The output twist ξ = [v; ω] follows the convention:
|
|
113
|
+
|
|
114
|
+
- v: 3D linear component
|
|
115
|
+
- ω: 3D angular component (rotation vector)
|
|
116
|
+
|
|
117
|
+
This is useful for computing error metrics between poses in optimization.
|
|
118
|
+
|
|
119
|
+
Attributes:
|
|
120
|
+
transform: 4×4 homogeneous transformation matrix with shape (4, 4)
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
Compute pose error for trajectory optimization::
|
|
124
|
+
|
|
125
|
+
import openscvx as ox
|
|
126
|
+
|
|
127
|
+
T_current = forward_kinematics(q)
|
|
128
|
+
T_target = ox.Parameter("T_target", shape=(4, 4), value=goal_pose)
|
|
129
|
+
|
|
130
|
+
# Relative transformation
|
|
131
|
+
T_error = ox.linalg.inv(T_target) @ T_current
|
|
132
|
+
|
|
133
|
+
# Convert to twist for error metric
|
|
134
|
+
twist_error = ox.lie.SE3Log(T_error)
|
|
135
|
+
pose_cost = ox.linalg.Norm(twist_error) ** 2
|
|
136
|
+
|
|
137
|
+
See Also:
|
|
138
|
+
- SE3Exp: Inverse operation (twist to transformation matrix)
|
|
139
|
+
- SO3Log: Rotation-only logarithm map
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(self, transform):
|
|
143
|
+
"""Initialize SE3 logarithm map.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
transform: 4×4 homogeneous transformation matrix with shape (4, 4)
|
|
147
|
+
"""
|
|
148
|
+
self.transform = to_expr(transform)
|
|
149
|
+
|
|
150
|
+
def children(self):
|
|
151
|
+
return [self.transform]
|
|
152
|
+
|
|
153
|
+
def canonicalize(self) -> "Expr":
|
|
154
|
+
transform = self.transform.canonicalize()
|
|
155
|
+
return SE3Log(transform)
|
|
156
|
+
|
|
157
|
+
def check_shape(self) -> Tuple[int, ...]:
|
|
158
|
+
"""Check that input is a 4×4 matrix and return output shape.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
tuple: Shape (6,) for the twist vector
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
ValueError: If transform does not have shape (4, 4)
|
|
165
|
+
"""
|
|
166
|
+
transform_shape = self.transform.check_shape()
|
|
167
|
+
if transform_shape != (4, 4):
|
|
168
|
+
raise ValueError(f"SE3Log expects transform with shape (4, 4), got {transform_shape}")
|
|
169
|
+
return (6,)
|
|
170
|
+
|
|
171
|
+
def __repr__(self):
|
|
172
|
+
return f"SE3Log({self.transform!r})"
|