2222# from spatialmath.base.transforms2d import transl2
2323
2424
25- try : # pragma: no cover
26- # print('Using SymPy')
27- from sympy import Matrix
28-
29- _symbolics = True
30-
31- except ImportError : # pragma: no cover
32- _symbolics = False
25+ from spatialmath .base import symbolic as sym
3326
3427_eps = np .finfo (np .float64 ).eps
3528
@@ -378,10 +371,34 @@ def isR(R: NDArray, tol: float = 20) -> bool: # -> TypeGuard[SOnArray]:
378371
379372 :seealso: isrot2, isrot
380373 """
381- return bool (
382- np .linalg .norm (R @ R .T - np .eye (R .shape [0 ])) < tol * _eps
383- and np .linalg .det (R ) > 0
384- )
374+ n = R .shape [0 ]
375+ if n == 3 :
376+ # explicit cofactor expansion avoids the dispatch overhead of
377+ # np.linalg.det/norm, which dominates cost for such a small matrix
378+ det = (
379+ R [0 , 0 ] * (R [1 , 1 ] * R [2 , 2 ] - R [1 , 2 ] * R [2 , 1 ])
380+ - R [0 , 1 ] * (R [1 , 0 ] * R [2 , 2 ] - R [1 , 2 ] * R [2 , 0 ])
381+ + R [0 , 2 ] * (R [1 , 0 ] * R [2 , 1 ] - R [1 , 1 ] * R [2 , 0 ])
382+ )
383+ if det <= 0 :
384+ return False
385+ D = R @ R .T
386+ D [0 , 0 ] -= 1.0
387+ D [1 , 1 ] -= 1.0
388+ D [2 , 2 ] -= 1.0
389+ return bool (np .sum (D * D ) < (tol * _eps ) ** 2 )
390+ elif n == 2 :
391+ det = R [0 , 0 ] * R [1 , 1 ] - R [0 , 1 ] * R [1 , 0 ]
392+ if det <= 0 :
393+ return False
394+ D = R @ R .T
395+ D [0 , 0 ] -= 1.0
396+ D [1 , 1 ] -= 1.0
397+ return bool (np .sum (D * D ) < (tol * _eps ) ** 2 )
398+ else :
399+ return bool (
400+ np .linalg .norm (R @ R .T - np .eye (n )) < tol * _eps and np .linalg .det (R ) > 0
401+ )
385402
386403
387404def isskew (S : NDArray , tol : float = 20 ) -> bool : # -> TypeGuard[sonArray]:
@@ -408,6 +425,11 @@ def isskew(S: NDArray, tol: float = 20) -> bool: # -> TypeGuard[sonArray]:
408425
409426 :seealso: isskewa
410427 """
428+ # NB: unlike isR/isskewa, an explicit-arithmetic fast path here did not
429+ # show a reliable win under careful (min-of-repeats) benchmarking - the
430+ # only overhead being avoided is np.linalg.norm on an already-cheap
431+ # `S + S.T`, not enough to reliably beat the scalar-indexing cost of
432+ # unrolling it by hand. Left as the original implementation.
411433 return bool (np .linalg .norm (S + S .T ) < tol * _eps )
412434
413435
@@ -436,9 +458,42 @@ def isskewa(S: NDArray, tol: float = 20) -> bool: # -> TypeGuard[senArray]:
436458
437459 :seealso: isskew
438460 """
439- return bool (np .linalg .norm (S [0 :- 1 , 0 :- 1 ] + S [0 :- 1 , 0 :- 1 ].T ) < tol * _eps ) and all (
440- S [- 1 , :] == 0
441- )
461+ n = S .shape [0 ]
462+ if n == 4 :
463+ # explicit sum-of-squares + scalar bottom-row check avoids the
464+ # generic-dispatch overhead of np.linalg.norm and the array
465+ # allocation + all() of the bottom-row comparison, same as isR/ishom
466+ r00 = S [0 , 0 ] + S [0 , 0 ]
467+ r01 = S [0 , 1 ] + S [1 , 0 ]
468+ r02 = S [0 , 2 ] + S [2 , 0 ]
469+ r11 = S [1 , 1 ] + S [1 , 1 ]
470+ r12 = S [1 , 2 ] + S [2 , 1 ]
471+ r22 = S [2 , 2 ] + S [2 , 2 ]
472+ resid = r00 * r00 + r11 * r11 + r22 * r22 + 2.0 * (
473+ r01 * r01 + r02 * r02 + r12 * r12
474+ )
475+ return bool (
476+ resid < (tol * _eps ) ** 2
477+ and S [3 , 0 ] == 0
478+ and S [3 , 1 ] == 0
479+ and S [3 , 2 ] == 0
480+ and S [3 , 3 ] == 0
481+ )
482+ elif n == 3 :
483+ r00 = S [0 , 0 ] + S [0 , 0 ]
484+ r01 = S [0 , 1 ] + S [1 , 0 ]
485+ r11 = S [1 , 1 ] + S [1 , 1 ]
486+ resid = r00 * r00 + r11 * r11 + 2.0 * r01 * r01
487+ return bool (
488+ resid < (tol * _eps ) ** 2
489+ and S [2 , 0 ] == 0
490+ and S [2 , 1 ] == 0
491+ and S [2 , 2 ] == 0
492+ )
493+ else :
494+ return bool (
495+ np .linalg .norm (S [0 :- 1 , 0 :- 1 ] + S [0 :- 1 , 0 :- 1 ].T ) < tol * _eps
496+ ) and all (S [- 1 , :] == 0 )
442497
443498
444499def iseye (S : NDArray , tol : float = 20 ) -> bool :
@@ -843,10 +898,12 @@ def det(m: np.ndarray) -> float:
843898
844899 :SymPy: supported
845900 """
846- if m .dtype .kind == "O" and _symbolics :
847- return Matrix (m ).det ()
848- else :
849- return np .linalg .det (m )
901+ if m .dtype .kind == "O" :
902+ try :
903+ return sym .det (m )
904+ except ImportError :
905+ pass
906+ return np .linalg .det (m )
850907
851908
852909if __name__ == "__main__" : # pragma: no cover
0 commit comments