Skip to content

Commit 82b2dd6

Browse files
authored
Defer SymPy loading in transformsNd.py
1 parent 81c4ee4 commit 82b2dd6

1 file changed

Lines changed: 76 additions & 19 deletions

File tree

‎spatialmath/base/transformsNd.py‎

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,7 @@
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

387404
def 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

444499
def 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

852909
if __name__ == "__main__": # pragma: no cover

0 commit comments

Comments
 (0)