Skip to content

Commit 07a60f4

Browse files
petercorkeclaude
andauthored
fix(ik): unify ik_XX/ikine_XX return types and fix IK docs (#652)
* fix(ik): add IKSolution.__getitem__/__repr__, fix docstring typos IKSolution had __iter__ but no __getitem__, so positional indexing (sol[0], sol[1], ...) -- the pattern every existing caller and the old bare-tuple return used -- raised TypeError. Add __getitem__ matching __iter__'s order, and a __repr__ matching the existing custom __str__ instead of the verbose default dataclass repr. Also fixes "Levemberg-Marquadt"/"Marquadt" -> "Levenberg-Marquardt" and "progamming" -> "programming", present throughout this file's docstrings. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(ik): unify ik_LM/ik_NR/ik_GN's return type with IKSolution ik_LM/ik_NR/ik_GN (the fast C++-backed solvers) returned a bare 5-tuple while ikine_LM/ikine_NR/ikine_GN (the pure-Python solvers) already returned IKSolution -- same family of methods, two different return shapes. Wrap the C++ tuple in IKSolution in both ETS.ik_LM/ ik_NR/ik_GN and their RobotKinematics forwarders, and update the return-type annotations and :returns:/:rtype: docstring fields to match (ikine_LM/ikine_NR/ikine_GN/ikine_QP were missing :returns:/ :rtype: entirely -- added those too). Also: - bidirectionally cross-reference each ik_XX with its ikine_XX counterpart (previously only cross-referenced their C++ siblings) - add a loud warning to ik_LM/ik_NR/ik_GN's docstrings that they require the compiled C++ extension and raise RuntimeError without it (e.g. pure-Python builds, Pyodide/JupyterLite) - fix several copy-paste bugs in RobotKinematics.py found while doing this: ik_GN's own "See Also" listed itself instead of ik_LM/ik_NR, ikine_GN's and ikine_QP's listed the wrong solver class entirely (IK_NR instead of IK_GN/IK_QP), and two runblock examples said "ikine_GN"/"ikine_LM" while actually calling ik_NR/ik_LM/ik_GN - fix "Levemberg-Marquadt"/"Marquadt" -> "Levenberg-Marquardt" and "deined" -> "defined" throughout both files Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(dhrobot): remove 5 completely broken dead IK methods DHRobot.ik_lm_chan/ik_lm_wampler/ik_lm_sugihara/ik_nr/ik_gn all forwarded to self.ets().<same-name>(...), but ETS has never had methods by these names (only the unified ik_LM/ik_NR/ik_GN, each taking a method= kwarg where relevant) -- every one of these five methods raises AttributeError unconditionally on any call. Their docstrings even have literal ":seealso: TODO" placeholders. No test exercises any of them. Confirmed dead: nothing in tests/ or docs/ references any of the five; the only caller was examples/ik_exp.py (fixed separately). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(examples): repair ik_exp.py to use the real ik_NR/ik_GN/ik_LM API Called the now-removed dead ets.ik_nr/ik_gn/ik_lm_chan/ik_lm_wampler/ ik_lm_sugihara methods, which never existed on ETS in the first place (same root cause as the DHRobot dead-method removal). Rewired to the real ik_NR/ik_GN/ik_LM(method=...) API with matching parameter names, switched from raw 5-tuple unpacking to IKSolution attribute access (the old unpacking would have silently broken now that these methods return a 6-field IKSolution instead of a 5-tuple), and dropped several entirely unused imports (fknm, swift, spatialgeometry, sys, and unused typing names). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs: fix Levenberg-Marquardt spelling in IK docs Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(ik): add regression tests for IKSolution/ik_XX return-type changes Covers IKSolution.__getitem__/__repr__, and that ik_LM/ik_NR/ik_GN now return real IKSolution instances rather than a bare tuple. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 2e2bfe2 commit 07a60f4

8 files changed

Lines changed: 259 additions & 741 deletions

File tree

docs/source/IK/ik_lm.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
IK_LM - Levemberg-Marquadt Numerical IK
2-
---------------------------------------
1+
IK_LM - Levenberg-Marquardt Numerical IK
2+
----------------------------------------
33

44
.. currentmodule:: roboticstoolbox.robot.IK
55

docs/source/intro.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ All robots can generate a random joint configuration informed by joint limits, i
103103
>>> puma.random_q()
104104

105105
``ikine_LM`` is a generalised iterative numerical solution based on
106-
Levenberg-Marquadt minimization, and additional status results are also
106+
Levenberg-Marquardt minimization, and additional status results are also
107107
returned as part of a named tuple.
108108

109109
.. warning::

examples/ik_exp.py

Lines changed: 30 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
11
import numpy as np
22
import roboticstoolbox as rtb
3-
import spatialmath as sm
4-
import fknm
5-
import time
6-
import swift
7-
import spatialgeometry as sg
8-
import sys
93
from ansitable import ANSITable
104

11-
from numpy import ndarray
12-
from spatialmath import SE3
13-
from typing import Union, overload, List, Set
14-
155
# Our robot and ETS
166
robot = rtb.models.Panda()
177
ets = robot.ets()
@@ -42,7 +32,7 @@
4232
tol = 1e-6
4333

4434
# Reject solutions with invalid joint limits
45-
reject_jl = True
35+
joint_limits = True
4636

4737

4838
class IK:
@@ -64,164 +54,74 @@ def __init__(self, name, solve, problems=problems):
6454

6555

6656
solvers = [
67-
# IK(
68-
# "Newton Raphson",
69-
# lambda Tep: ets.ik_nr(
70-
# Tep,
71-
# q0=None,
72-
# ilimit=ilimit,
73-
# slimit=slimit,
74-
# tol=tol,
75-
# reject_jl=reject_jl,
76-
# we=we,
77-
# use_pinv=False,
78-
# pinv_damping=0.0,
79-
# ),
80-
# ),
81-
# IK(
82-
# "Gauss Newton",
83-
# lambda Tep: ets.ik_gn(
84-
# Tep,
85-
# q0=None,
86-
# ilimit=ilimit,
87-
# slimit=slimit,
88-
# tol=tol,
89-
# reject_jl=reject_jl,
90-
# we=we,
91-
# use_pinv=False,
92-
# pinv_damping=0.0,
93-
# ),
94-
# ),
9557
IK(
9658
"Newton Raphson Pinv",
97-
lambda Tep: ets.ik_nr(
59+
lambda Tep: ets.ik_NR(
9860
Tep,
9961
q0=None,
10062
ilimit=ilimit,
10163
slimit=slimit,
10264
tol=tol,
103-
reject_jl=reject_jl,
104-
we=we,
105-
use_pinv=True,
65+
joint_limits=joint_limits,
66+
mask=we,
67+
pinv=True,
10668
pinv_damping=0.0,
10769
),
10870
),
10971
IK(
11072
"Gauss Newton Pinv",
111-
lambda Tep: ets.ik_gn(
73+
lambda Tep: ets.ik_GN(
11274
Tep,
11375
q0=None,
11476
ilimit=ilimit,
11577
slimit=slimit,
11678
tol=tol,
117-
reject_jl=reject_jl,
118-
we=we,
119-
use_pinv=True,
79+
joint_limits=joint_limits,
80+
mask=we,
81+
pinv=True,
12082
pinv_damping=0.0,
12183
),
12284
),
12385
IK(
12486
"LM Chan 0.1",
125-
lambda Tep: ets.ik_lm_chan(
87+
lambda Tep: ets.ik_LM(
12688
Tep,
12789
q0=None,
12890
ilimit=ilimit,
12991
slimit=slimit,
13092
tol=tol,
131-
reject_jl=reject_jl,
132-
we=we,
133-
λ=0.1,
93+
joint_limits=joint_limits,
94+
mask=we,
95+
k=0.1,
96+
method="chan",
13497
),
13598
),
136-
# IK(
137-
# "LM Chan 1.0",
138-
# lambda Tep: ets.ik_lm_chan(
139-
# Tep,
140-
# q0=None,
141-
# ilimit=ilimit,
142-
# slimit=slimit,
143-
# tol=tol,
144-
# reject_jl=reject_jl,
145-
# we=we,
146-
# λ=1.0,
147-
# ),
148-
# ),
149-
# IK(
150-
# "LM Wampler",
151-
# lambda Tep: ets.ik_lm_wampler(
152-
# Tep,
153-
# q0=None,
154-
# ilimit=ilimit,
155-
# slimit=slimit,
156-
# tol=tol,
157-
# reject_jl=reject_jl,
158-
# we=we,
159-
# λ=1e-2,
160-
# ),
161-
# ),
16299
IK(
163100
"LM Wampler 1e-4",
164-
lambda Tep: ets.ik_lm_wampler(
101+
lambda Tep: ets.ik_LM(
165102
Tep,
166103
q0=None,
167104
ilimit=ilimit,
168105
slimit=slimit,
169106
tol=tol,
170-
reject_jl=reject_jl,
171-
we=we,
172-
λ=1e-4,
107+
joint_limits=joint_limits,
108+
mask=we,
109+
k=1e-4,
110+
method="wampler",
173111
),
174112
),
175-
# IK(
176-
# "LM Wampler 1e-6",
177-
# lambda Tep: ets.ik_lm_wampler(
178-
# Tep,
179-
# q0=None,
180-
# ilimit=ilimit,
181-
# slimit=slimit,
182-
# tol=tol,
183-
# reject_jl=reject_jl,
184-
# we=we,
185-
# λ=1e-6,
186-
# ),
187-
# ),
188-
# IK(
189-
# "LM Sugihara 0.001",
190-
# lambda Tep: ets.ik_lm_sugihara(
191-
# Tep,
192-
# q0=None,
193-
# ilimit=ilimit,
194-
# slimit=slimit,
195-
# tol=tol,
196-
# reject_jl=reject_jl,
197-
# we=we,
198-
# λ=0.001,
199-
# ),
200-
# ),
201-
# IK(
202-
# "LM Sugihara 0.01",
203-
# lambda Tep: ets.ik_lm_sugihara(
204-
# Tep,
205-
# q0=None,
206-
# ilimit=ilimit,
207-
# slimit=slimit,
208-
# tol=tol,
209-
# reject_jl=reject_jl,
210-
# we=we,
211-
# λ=0.01,
212-
# ),
213-
# ),
214113
IK(
215114
"LM Sugihara 0.1",
216-
lambda Tep: ets.ik_lm_sugihara(
115+
lambda Tep: ets.ik_LM(
217116
Tep,
218117
q0=None,
219118
ilimit=ilimit,
220119
slimit=slimit,
221120
tol=tol,
222-
reject_jl=reject_jl,
223-
we=we,
224-
λ=0.1,
121+
joint_limits=joint_limits,
122+
mask=we,
123+
k=0.1,
124+
method="sugihara",
225125
),
226126
),
227127
]
@@ -230,13 +130,13 @@ def __init__(self, name, solve, problems=problems):
230130
print(i + 1)
231131

232132
for solver in solvers:
233-
_, success, iterations, searches, residual = solver.solve(Tep[i])
133+
sol = solver.solve(Tep[i])
234134

235-
if success:
236-
solver.success[i] = success
237-
solver.iterations[i] = iterations
238-
solver.searches[i] = searches
239-
solver.residual[i] = residual
135+
if sol.success:
136+
solver.success[i] = sol.success
137+
solver.iterations[i] = sol.iterations
138+
solver.searches[i] = sol.searches
139+
solver.residual[i] = sol.residual
240140
solver.total_iterations += solver.iterations[i]
241141
solver.total_searches += solver.searches[i]
242142
else:

0 commit comments

Comments
 (0)