diff --git a/docs/source/conf.py b/docs/source/conf.py index 42e18296c..d06799bd0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -198,6 +198,7 @@ :format: html .. |BlockOptions| replace:: :raw-html:`common Block options` .. |GraphicsBlockOptions| replace:: :raw-html:`common GraphicsBlock options` +.. |ikargs| replace:: additional keyword arguments accepted by the underlying numerical IK solver -- ``ilimit``, ``slimit``, ``tol``, ``mask``, ``joint_limits``, ``seed``, ``k``, ``method``, ``kq``, ``km`` -- see :meth:`~roboticstoolbox.ETS.ikine_LM` for details of each """ # -------- Suppress common noisy warnings ----------------------------------------# diff --git a/examples/ikine_evaluate.py b/examples/ikine_evaluate.py index d4c4c2f80..8888a8051 100644 --- a/examples/ikine_evaluate.py +++ b/examples/ikine_evaluate.py @@ -31,31 +31,25 @@ # - the statement to execute for timeit ikfuncs = [ ( - robot.ikine_LM, # Levenberg-Marquadt + lambda T, q0: robot.ikine_LM(T, q0=q0), # Levenberg-Marquadt "ikine_LM", - "sol = robot.ikine_LM(T, q0)", + "sol = robot.ikine_LM(T, q0=q0)", ), ( - robot.ikine_LMS, # Levenberg-Marquadt (Sugihara) - "ikine_LMS", - "sol = robot.ikine_LMS(T, q0)", + lambda T, q0: robot.ikine_LM(T, q0=q0, method="sugihara", k=0.0001), + "ikine_LM (sugihara)", + "sol = robot.ikine_LM(T, q0=q0, method='sugihara', k=0.0001)", ), ( - robot.ikine_min, # numerical solution with no constraints - "ikine_min(qlim=False)", - "sol = robot.ikine_min(T, q0)", + lambda T, q0: robot.ikine_LM(T, q0=q0, joint_limits=False), + "ikine_LM(qlim=False)", + "sol = robot.ikine_LM(T, q0=q0, joint_limits=False)", ), ( - lambda T, q0: robot.ikine_min( - T, q0, qlim=True - ), # numerical solution with constraints - "ikine_min(qlim=True)", - "sol = robot.ikine_min(T, q0, qlim=True)", + lambda T, q0: robot.ikine_LM(T, q0=q0, joint_limits=True), + "ikine_LM(qlim=True)", + "sol = robot.ikine_LM(T, q0=q0, joint_limits=True)", ), - # (robot.ikine_mmc, #numerical solution with no constraints - # "ikine_min(qlim=False)", - # "sol = robot.ikine_min(T, q0)" - # ), ] if hasattr(robot, "ikine_a"): a = ( diff --git a/examples/readme.py b/examples/readme.py index ddfc1f790..86563ff1c 100644 --- a/examples/readme.py +++ b/examples/readme.py @@ -11,7 +11,9 @@ # IK T = SE3(0.7, 0.2, 0.1) * SE3.OA([0, 1, 0], [0, 0, -1]) -sol = robot.ikine_LMS(T) # solve IK, ignore additional outputs +sol = robot.ikine_LM( + T, method="sugihara", k=0.0001 +) # solve IK, ignore additional outputs print(sol.q) # display joint angles # FK shows that desired end-effector pose was achieved print(robot.fkine(sol.q)) diff --git a/src/roboticstoolbox/robot/DHRobot.py b/src/roboticstoolbox/robot/DHRobot.py index 5a04c8490..d1c0133fb 100644 --- a/src/roboticstoolbox/robot/DHRobot.py +++ b/src/roboticstoolbox/robot/DHRobot.py @@ -2526,23 +2526,24 @@ def ikine_LM( self, Tep: np.ndarray | SE3, q0: ArrayLike | None = None, - ilimit: int = 30, - slimit: int = 100, - tol: float = 1e-6, - joint_limits: bool = False, - mask: ArrayLike | None = None, - seed: int | None = None, + **ikargs, ): - return self.ets().ikine_LM( - Tep=Tep, - q0=q0, - ilimit=ilimit, - slimit=slimit, - tol=tol, - joint_limits=joint_limits, - mask=mask, - seed=seed, - ) + """ + Numerical inverse kinematics by Levenberg-Marquadt optimization + + :param Tep: The desired end-effector pose + :param q0: The initial joint coordinate vector + :param ikargs: |ikargs| + :returns: An IKSolution containing joint coordinates ``q``, ``success`` flag, ``iterations``, ``searches``, ``residual`` error value, and ``reason`` string if applicable + :rtype: IKSolution + + This is a thin wrapper around :meth:`ETS.ikine_LM` -- see that + method for the full description of the Levenberg-Marquadt solver + and its ``method``/``kq``/``km`` variants. + + .. seealso:: :meth:`ikine_LM` (:class:`~roboticstoolbox.ETS`) + """ + return self.ets().ikine_LM(Tep=Tep, q0=q0, **ikargs) class SerialLink(DHRobot): diff --git a/tests/test_DHRobot.py b/tests/test_DHRobot.py index 6b2357fe3..ea058fc3d 100644 --- a/tests/test_DHRobot.py +++ b/tests/test_DHRobot.py @@ -1029,14 +1029,18 @@ def test_ikine_LM(self): self.assertTrue(sol.success) self.assertAlmostEqual(np.linalg.norm(T - puma.fkine(sol.q)), 0, places=4) - # def test_ikine_LMS(self): - # puma = rp.models.DH.Puma560() + def test_ikine_LM_ikargs_forwarding(self): + # DHRobot.ikine_LM forwards **ikargs straight through to + # ETS.ikine_LM -- confirm a non-default method/k actually reaches + # the underlying solver (regression: DHRobot.ikine_LM used to have + # its own narrow signature that silently dropped method/k/kq/km). + puma = rp.models.DH.Puma560() - # T = puma.fkine(puma.qn) + T = puma.fkine(puma.qn) - # sol = puma.ikine_LM(T) - # self.assertTrue(sol.success) - # self.assertAlmostEqual(np.linalg.norm(T - puma.fkine(sol.q)), 0, places=6) + sol = puma.ikine_LM(T, method="sugihara", k=0.0001, tol=1e-10, seed=0) + self.assertTrue(sol.success) + self.assertAlmostEqual(np.linalg.norm(T - puma.fkine(sol.q)), 0, places=4) # def test_ikine_unc(self): # puma = rp.models.DH.Puma560()