55
66import numpy as np
77from collections import namedtuple
8+
89# from roboticstoolbox.tools.null import null
910import roboticstoolbox as rtb
1011from spatialmath import base
1314import math
1415import qpsolvers as qp
1516from roboticstoolbox .tools .p_servo import p_servo
17+
1618# iksol = namedtuple("IKsolution", "q, success, reason, iterations, residual",
1719# defaults=(None, False, None, None, None)) # Py >= 3.7 only
1820iksol = namedtuple ("IKsolution" , "q, success, reason, iterations, residual" )
2123
2224
2325class IKMixin :
24-
25- def ikine_mmc (
26- self , T ,
27- q0 = None ):
26+ def ikine_mmc (self , T , q0 = None ):
2827
2928 arrived = False
3029
@@ -104,20 +103,22 @@ def ikine_mmc(
104103
105104 return q
106105
107- # --------------------------------------------------------------------- #
106+ # --------------------------------------------------------------------- #
108107
109108 def ikine_LM (
110- self , T ,
111- q0 = None ,
112- mask = None ,
113- ilimit = 500 ,
114- rlimit = 100 ,
115- tol = 1e-10 ,
116- L = 0.1 ,
117- Lmin = 0 ,
118- search = False ,
119- slimit = 100 ,
120- transpose = None ):
109+ self ,
110+ T ,
111+ q0 = None ,
112+ mask = None ,
113+ ilimit = 500 ,
114+ rlimit = 100 ,
115+ tol = 1e-10 ,
116+ L = 0.1 ,
117+ Lmin = 0 ,
118+ search = False ,
119+ slimit = 100 ,
120+ transpose = None ,
121+ ):
121122 """
122123 Numerical inverse kinematics by Levenberg-Marquadt optimization
123124 (Robot superclass)
@@ -245,7 +246,7 @@ def ikine_LM(
245246 """ # noqa E501
246247
247248 if not isinstance (T , SE3 ):
248- raise TypeError (' argument must be SE3' )
249+ raise TypeError (" argument must be SE3" )
249250
250251 solutions = []
251252
@@ -259,7 +260,7 @@ def ikine_LM(
259260 for k in range (slimit ):
260261 # choose a random joint coordinate
261262 q0_k = np .random .rand (self .n ) * qspan + qlim [0 , :]
262- print (' search starts at ' , q0_k )
263+ print (" search starts at " , q0_k )
263264
264265 # recurse into the solver
265266 solution = self .ikine_LM (
@@ -273,7 +274,8 @@ def ikine_LM(
273274 Lmin ,
274275 False ,
275276 slimit ,
276- transpose )
277+ transpose ,
278+ )
277279
278280 if solution .success :
279281 q0 = solution .q
@@ -295,13 +297,15 @@ def ikine_LM(
295297 if mask is not None :
296298 mask = base .getvector (mask , 6 )
297299 if not self .n >= np .sum (mask ):
298- raise ValueError ('Number of robot DOF must be >= the number '
299- 'of 1s in the mask matrix' )
300+ raise ValueError (
301+ "Number of robot DOF must be >= the number "
302+ "of 1s in the mask matrix"
303+ )
300304 else :
301305 mask = np .ones (6 )
302306 W = np .diag (mask )
303307
304- tcount = 0 # Total iteration count
308+ tcount = 0 # Total iteration count
305309 rejcount = 0 # Rejected step count
306310 nm = 0
307311 revolutes = self .revolutejoints
@@ -332,17 +336,20 @@ def ikine_LM(
332336
333337 if transpose is not None :
334338 # Do the simple Jacobian transpose with constant gain
335- dq = transpose * J .T @ e # lgtm [py/multiple-definition]
339+ dq = transpose * J .T @ e # lgtm [py/multiple-definition]
336340 q += dq
337341 else :
338342 # Do the damped inverse Gauss-Newton with
339343 # Levenberg-Marquadt
340344 # dq = np.linalg.inv(
341345 # JtJ + ((Li + Lmin) * np.eye(self.n))
342346 # ) @ J.T @ W @ e
343- dq = np .linalg .inv (
344- JtJ + ((Li + Lmin ) * np .diag (np .diag (JtJ )))
345- ) @ J .T @ W @ e
347+ dq = (
348+ np .linalg .inv (JtJ + ((Li + Lmin ) * np .diag (np .diag (JtJ ))))
349+ @ J .T
350+ @ W
351+ @ e
352+ )
346353 # print(J.T @ W @ e)
347354
348355 # Compute possible new value of
@@ -371,7 +378,7 @@ def ikine_LM(
371378 q [k ] -= 2 * np .pi
372379
373380 k = np .logical_and (q < - np .pi , revolutes )
374- q [k ] += + 2 * np .pi
381+ q [k ] += + 2 * np .pi
375382
376383 nm = np .linalg .norm (W @ e )
377384 # qs = ", ".join(["{:8.3f}".format(qi) for qi in q])
@@ -392,20 +399,12 @@ def ikine_LM(
392399 np .array ([sol .success for sol in solutions ]),
393400 [sol .reason for sol in solutions ],
394401 np .array ([sol .iterations for sol in solutions ]),
395- np .array ([sol .residual for sol in solutions ])
402+ np .array ([sol .residual for sol in solutions ]),
396403 )
397404
398- # --------------------------------------------------------------------- #
405+ # --------------------------------------------------------------------- #
399406
400- def ikine_LMS (
401- self , T ,
402- q0 = None ,
403- mask = None ,
404- ilimit = 500 ,
405- tol = 1e-10 ,
406- wN = 1e-3 ,
407- Lmin = 0
408- ):
407+ def ikine_LMS (self , T , q0 = None , mask = None , ilimit = 500 , tol = 1e-10 , wN = 1e-3 , Lmin = 0 ):
409408 """
410409 Numerical inverse kinematics by Levenberg-Marquadt optimization
411410 (Robot superclass)
@@ -511,7 +510,7 @@ def ikine_LMS(
511510 """ # noqa E501
512511
513512 if not isinstance (T , SE3 ):
514- raise TypeError (' argument must be SE3' )
513+ raise TypeError (" argument must be SE3" )
515514
516515 solutions = []
517516
@@ -523,8 +522,10 @@ def ikine_LMS(
523522 if mask is not None :
524523 mask = base .getvector (mask , 6 )
525524 if not self .n >= np .sum (mask ):
526- raise ValueError ('Number of robot DOF must be >= the number '
527- 'of 1s in the mask matrix' )
525+ raise ValueError (
526+ "Number of robot DOF must be >= the number "
527+ "of 1s in the mask matrix"
528+ )
528529 else :
529530 mask = np .ones (6 )
530531 W = np .diag (mask )
@@ -555,7 +556,7 @@ def ikine_LMS(
555556 J = self .jacob0 (q )
556557 WN = E * np .eye (self .n ) + wN * np .eye (self .n )
557558 H = J .T @ W @ J + WN # n x n
558- g = J .T @ W @ e # n x 1
559+ g = J .T @ W @ e # n x 1
559560
560561 # Compute new value of q
561562 q += np .linalg .inv (H ) @ g # n x 1
@@ -570,7 +571,7 @@ def ikine_LMS(
570571 q [k ] -= 2 * np .pi
571572
572573 k = np .logical_and (q < - np .pi , revolutes )
573- q [k ] += + 2 * np .pi
574+ q [k ] += + 2 * np .pi
574575
575576 # qs = ", ".join(["{:8.3f}".format(qi) for qi in q])
576577 # print(f"|e|={E:8.2g}, det(H)={np.linalg.det(H)}: q={qs}")
@@ -590,14 +591,23 @@ def ikine_LMS(
590591 np .array ([sol .success for sol in solutions ]),
591592 [sol .reason for sol in solutions ],
592593 np .array ([sol .iterations for sol in solutions ]),
593- np .array ([sol .residual for sol in solutions ])
594+ np .array ([sol .residual for sol in solutions ]),
594595 )
595596
596- # --------------------------------------------------------------------- #
597+ # --------------------------------------------------------------------- #
597598
598599 def ikine_min (
599- self , T , q0 = None , qlim = False , ilimit = 1000 ,
600- tol = 1e-16 , method = None , stiffness = 0 , costfun = None , options = {}):
600+ self ,
601+ T ,
602+ q0 = None ,
603+ qlim = False ,
604+ ilimit = 1000 ,
605+ tol = 1e-16 ,
606+ method = None ,
607+ stiffness = 0 ,
608+ costfun = None ,
609+ options = {},
610+ ):
601611 r"""
602612 Inverse kinematics by optimization with joint limits (Robot superclass)
603613
@@ -703,7 +713,7 @@ def ikine_min(
703713 """ # noqa E501
704714
705715 if not isinstance (T , SE3 ):
706- raise TypeError (' argument must be SE3' )
716+ raise TypeError (" argument must be SE3" )
707717
708718 if q0 is None :
709719 q0 = np .zeros ((self .n ))
@@ -715,35 +725,35 @@ def ikine_min(
715725 wr = 1 / self .reach
716726 weight = np .r_ [wr , wr , wr , 1 , 1 , 1 ]
717727
718- optdict = {' maxiter' : ilimit }
728+ optdict = {" maxiter" : ilimit }
719729 if options is not None and isinstance (options , dict ):
720730 optdict .update (options )
721731 else :
722- raise ValueError (' options must be a dict' )
732+ raise ValueError (" options must be a dict" )
723733
724734 if qlim :
725735 # dealing with joint limits
726736 bounds = opt .Bounds (self .qlim [0 , :], self .qlim [1 , :])
727737
728738 if method is None :
729- method = ' trust-constr'
739+ method = " trust-constr"
730740 else :
731741 # no joint limits
732742 if method is None :
733- method = ' SLSQP'
743+ method = " SLSQP"
734744 bounds = None
735745
736746 def cost (q , T , weight , costfun , stiffness ):
737747 # T, weight, costfun, stiffness = args
738748 e = _angle_axis (self .fkine (q ).A , T ) * weight
739- E = (e ** 2 ).sum ()
749+ E = (e ** 2 ).sum ()
740750
741751 if stiffness > 0 :
742752 # Enforce a continuity constraint on joints, minimum bend
743- E += np .sum (np .diff (q )** 2 ) * stiffness
753+ E += np .sum (np .diff (q ) ** 2 ) * stiffness
744754
745755 if costfun is not None :
746- E += (e ** 2 ).sum () + costfun (q )
756+ E += (e ** 2 ).sum () + costfun (q )
747757
748758 return E
749759
@@ -755,7 +765,7 @@ def cost(q, T, weight, costfun, stiffness):
755765 bounds = bounds ,
756766 method = method ,
757767 tol = tol ,
758- options = options
768+ options = options ,
759769 )
760770
761771 # trust-constr seems to work better than L-BFGS-B which often
@@ -772,11 +782,11 @@ def cost(q, T, weight, costfun, stiffness):
772782 else :
773783 return solutions
774784
775- # --------------------------------------------------------------------- #
785+ # --------------------------------------------------------------------- #
776786
777787 def ikine_global (
778- self , T , qlim = False , ilimit = 1000 ,
779- tol = 1e-16 , method = None , options = {} ):
788+ self , T , qlim = False , ilimit = 1000 , tol = 1e-16 , method = None , options = {}
789+ ):
780790 r"""
781791 .. warning:: Experimental code for using SciPy global optimizers.
782792
@@ -792,7 +802,7 @@ def ikine_global(
792802 # dual_annealing: bounds
793803
794804 if not isinstance (T , SE3 ):
795- raise TypeError (' argument must be SE3' )
805+ raise TypeError (" argument must be SE3" )
796806
797807 solutions = []
798808
@@ -802,29 +812,32 @@ def ikine_global(
802812 optdict = {}
803813
804814 if method is None :
805- method = ' differential-evolution'
815+ method = " differential-evolution"
806816
807- if method == ' brute' :
817+ if method == " brute" :
808818 # requires a tuple of tuples
809- optdict [' ranges' ] = tuple ([tuple (li .qlim ) for li in self ])
819+ optdict [" ranges" ] = tuple ([tuple (li .qlim ) for li in self ])
810820 else :
811- optdict [' bounds' ] = tuple ([tuple (li .qlim ) for li in self ])
821+ optdict [" bounds" ] = tuple ([tuple (li .qlim ) for li in self ])
812822
813- if method not in ['basinhopping' , 'brute' , 'differential_evolution' ,
814- 'shgo' , 'dual_annealing' ]:
815- raise ValueError ('unknown global optimizer requested' )
823+ if method not in [
824+ "basinhopping" ,
825+ "brute" ,
826+ "differential_evolution" ,
827+ "shgo" ,
828+ "dual_annealing" ,
829+ ]:
830+ raise ValueError ("unknown global optimizer requested" )
816831
817832 global_minimizer = opt .__dict__ [method ]
818833
819834 def cost (q , T , weight ):
820835 # T, weight, costfun, stiffness = args
821836 e = _angle_axis (self .fkine (q ).A , T ) * weight
822- return (e ** 2 ).sum ()
837+ return (e ** 2 ).sum ()
823838
824839 for _ in T :
825- res = global_minimizer (
826- cost ,
827- ** optdict )
840+ res = global_minimizer (cost , ** optdict )
828841
829842 solution = iksol (res .x , res .success , res .message , res .nit , res .fun )
830843 solutions .append (solution )
@@ -875,14 +888,11 @@ def _angle_axis_sekiguchi(T, Td):
875888 if R [1 , 0 ] > 0 and R [2 , 1 ] > 0 and R [0 , 2 ] > 0 :
876889 a = np .pi / np .sqrt (2 ) * np .sqrt (np .diag (R ) + 1 )
877890 elif R [1 , 0 ] > 0 : # (2)
878- a = np .pi / np .sqrt (2 ) * np .sqrt (
879- np .diag (R ) @ np .r_ [1 , 1 , - 1 ] + 1 )
891+ a = np .pi / np .sqrt (2 ) * np .sqrt (np .diag (R ) @ np .r_ [1 , 1 , - 1 ] + 1 )
880892 elif R [0 , 2 ] > 0 : # (3)
881- a = np .pi / np .sqrt (2 ) * np .sqrt (
882- np .diag (R ) @ np .r_ [1 , - 1 , 1 ] + 1 )
893+ a = np .pi / np .sqrt (2 ) * np .sqrt (np .diag (R ) @ np .r_ [1 , - 1 , 1 ] + 1 )
883894 elif R [2 , 1 ] > 0 : # (4)
884- a = np .pi / np .sqrt (2 ) * np .sqrt (
885- np .diag (R ) @ np .r_ [- 1 , 1 , 1 ] + 1 )
895+ a = np .pi / np .sqrt (2 ) * np .sqrt (np .diag (R ) @ np .r_ [- 1 , 1 , 1 ] + 1 )
886896 else :
887897 # non-diagonal matrix case
888898 ln = base .norm (li )
@@ -916,4 +926,4 @@ def _angle_axis_sekiguchi(T, Td):
916926 # print(robot.fkine(sol.q))
917927 # robot.plot(sol.q)
918928
919- sol = robot .ikine_global (T , method = ' brute' )
929+ sol = robot .ikine_global (T , method = " brute" )
0 commit comments