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.

Files changed (79) hide show
  1. openscvx/__init__.py +123 -0
  2. openscvx/_version.py +34 -0
  3. openscvx/algorithms/__init__.py +92 -0
  4. openscvx/algorithms/autotuning.py +24 -0
  5. openscvx/algorithms/base.py +351 -0
  6. openscvx/algorithms/optimization_results.py +215 -0
  7. openscvx/algorithms/penalized_trust_region.py +384 -0
  8. openscvx/config.py +437 -0
  9. openscvx/discretization/__init__.py +47 -0
  10. openscvx/discretization/discretization.py +236 -0
  11. openscvx/expert/__init__.py +23 -0
  12. openscvx/expert/byof.py +326 -0
  13. openscvx/expert/lowering.py +419 -0
  14. openscvx/expert/validation.py +357 -0
  15. openscvx/integrators/__init__.py +48 -0
  16. openscvx/integrators/runge_kutta.py +281 -0
  17. openscvx/lowered/__init__.py +30 -0
  18. openscvx/lowered/cvxpy_constraints.py +23 -0
  19. openscvx/lowered/cvxpy_variables.py +124 -0
  20. openscvx/lowered/dynamics.py +34 -0
  21. openscvx/lowered/jax_constraints.py +133 -0
  22. openscvx/lowered/parameters.py +54 -0
  23. openscvx/lowered/problem.py +70 -0
  24. openscvx/lowered/unified.py +718 -0
  25. openscvx/plotting/__init__.py +63 -0
  26. openscvx/plotting/plotting.py +756 -0
  27. openscvx/plotting/scp_iteration.py +299 -0
  28. openscvx/plotting/viser/__init__.py +126 -0
  29. openscvx/plotting/viser/animated.py +605 -0
  30. openscvx/plotting/viser/plotly_integration.py +333 -0
  31. openscvx/plotting/viser/primitives.py +355 -0
  32. openscvx/plotting/viser/scp.py +459 -0
  33. openscvx/plotting/viser/server.py +112 -0
  34. openscvx/problem.py +734 -0
  35. openscvx/propagation/__init__.py +60 -0
  36. openscvx/propagation/post_processing.py +104 -0
  37. openscvx/propagation/propagation.py +248 -0
  38. openscvx/solvers/__init__.py +51 -0
  39. openscvx/solvers/cvxpy.py +226 -0
  40. openscvx/symbolic/__init__.py +9 -0
  41. openscvx/symbolic/augmentation.py +630 -0
  42. openscvx/symbolic/builder.py +492 -0
  43. openscvx/symbolic/constraint_set.py +92 -0
  44. openscvx/symbolic/expr/__init__.py +222 -0
  45. openscvx/symbolic/expr/arithmetic.py +517 -0
  46. openscvx/symbolic/expr/array.py +632 -0
  47. openscvx/symbolic/expr/constraint.py +796 -0
  48. openscvx/symbolic/expr/control.py +135 -0
  49. openscvx/symbolic/expr/expr.py +720 -0
  50. openscvx/symbolic/expr/lie/__init__.py +87 -0
  51. openscvx/symbolic/expr/lie/adjoint.py +357 -0
  52. openscvx/symbolic/expr/lie/se3.py +172 -0
  53. openscvx/symbolic/expr/lie/so3.py +138 -0
  54. openscvx/symbolic/expr/linalg.py +279 -0
  55. openscvx/symbolic/expr/math.py +699 -0
  56. openscvx/symbolic/expr/spatial.py +209 -0
  57. openscvx/symbolic/expr/state.py +607 -0
  58. openscvx/symbolic/expr/stl.py +136 -0
  59. openscvx/symbolic/expr/variable.py +321 -0
  60. openscvx/symbolic/hashing.py +112 -0
  61. openscvx/symbolic/lower.py +760 -0
  62. openscvx/symbolic/lowerers/__init__.py +106 -0
  63. openscvx/symbolic/lowerers/cvxpy.py +1302 -0
  64. openscvx/symbolic/lowerers/jax.py +1382 -0
  65. openscvx/symbolic/preprocessing.py +757 -0
  66. openscvx/symbolic/problem.py +110 -0
  67. openscvx/symbolic/time.py +116 -0
  68. openscvx/symbolic/unified.py +420 -0
  69. openscvx/utils/__init__.py +20 -0
  70. openscvx/utils/cache.py +131 -0
  71. openscvx/utils/caching.py +210 -0
  72. openscvx/utils/printing.py +301 -0
  73. openscvx/utils/profiling.py +37 -0
  74. openscvx/utils/utils.py +100 -0
  75. openscvx-0.3.2.dev170.dist-info/METADATA +350 -0
  76. openscvx-0.3.2.dev170.dist-info/RECORD +79 -0
  77. openscvx-0.3.2.dev170.dist-info/WHEEL +5 -0
  78. openscvx-0.3.2.dev170.dist-info/licenses/LICENSE +201 -0
  79. 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})"