Skip to content

Commit 5fb7852

Browse files
authored
Defer optional SymPy loading until symbolic use
1 parent 2de7801 commit 5fb7852

1 file changed

Lines changed: 73 additions & 66 deletions

File tree

‎spatialmath/base/symbolic.py‎

Lines changed: 73 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -11,61 +11,67 @@
1111
supported.
1212
"""
1313

14+
from __future__ import annotations
15+
1416
import math
17+
from typing import TYPE_CHECKING
1518
from spatialmath.base.types import *
1619

17-
try: # pragma: no cover
18-
# print('Using SymPy')
19-
import sympy
20+
import importlib as _importlib
21+
import sys as _sys
2022

21-
_symbolics = True
22-
symtype = (sympy.Expr,)
23+
if TYPE_CHECKING:
2324
from sympy import Symbol
2425

25-
except ImportError: # pragma: no cover
26-
# SymPy is not installed
27-
_symbolics = False
28-
symtype = ()
29-
Symbol = Any
3026

27+
def _sympy():
28+
"""Load the optional dependency only for a symbolic operation."""
29+
return _importlib.import_module("sympy")
3130

32-
# ---------------------------------------------------------------------------------------#
3331

34-
if _symbolics:
32+
def __getattr__(name):
33+
"""Keep explicit access to the SymPy type aliases lazy."""
34+
if name == "Symbol":
35+
return _sympy().Symbol
36+
if name == "symtype":
37+
try:
38+
return (_sympy().Expr,)
39+
except ImportError:
40+
return ()
41+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
3542

36-
def symbol(
37-
name: str, real: Optional[bool] = True
38-
) -> Union[Symbol, Tuple[Symbol, ...]]:
39-
"""
40-
Create symbolic variables
4143

42-
:param name: symbol names
43-
:type name: str
44-
:param real: assume variable is real, defaults to True
45-
:type real: bool, optional
46-
:return: SymPy symbols
47-
:rtype: sympy
44+
def symbol(name: str, real: Optional[bool] = True) -> Union[Symbol, Tuple[Symbol, ...]]:
45+
"""
46+
Create symbolic variables
4847
49-
.. runblock:: pycon
48+
:param name: symbol names
49+
:type name: str
50+
:param real: assume variable is real, defaults to True
51+
:type real: bool, optional
52+
:return: SymPy symbols
53+
:rtype: sympy
5054
51-
>>> from spatialmath.base.symbolic import *
52-
>>> theta = symbol('theta')
53-
>>> theta
54-
>>> theta, psi = symbol('theta psi')
55-
>>> theta
56-
>>> psi
57-
>>> q = symbol('q_:6')
58-
>>> q
55+
.. runblock:: pycon
56+
57+
>>> from spatialmath.base.symbolic import *
58+
>>> theta = symbol('theta')
59+
>>> theta
60+
>>> theta, psi = symbol('theta psi')
61+
>>> theta
62+
>>> psi
63+
>>> q = symbol('q_:6')
64+
>>> q
5965
60-
.. note:: In Jupyter symbols are pretty printed.
66+
.. note:: In Jupyter symbols are pretty printed.
6167
62-
- symbols named after greek letters will appear as greek letters
63-
- underscore means subscript as it does in LaTex, so the symbols ``q``
64-
above will be subscripted.
68+
- symbols named after greek letters will appear as greek letters
69+
- underscore means subscript as it does in LaTex, so the symbols ``q``
70+
above will be subscripted.
6571
66-
:seealso: :func:`sympy.symbols`
67-
"""
68-
return sympy.symbols(name, real=real)
72+
:seealso: :func:`sympy.symbols`
73+
"""
74+
return _sympy().symbols(name, real=real)
6975

7076

7177
def issymbol(var: Any) -> bool:
@@ -84,13 +90,12 @@ def issymbol(var: Any) -> bool:
8490
>>> issymbol(3.4)
8591
8692
"""
87-
if _symbolics:
88-
if isinstance(var, (list, tuple)):
89-
return any([isinstance(x, symtype) for x in var])
90-
else:
91-
return isinstance(var, symtype)
92-
else:
93+
sympy = _sys.modules.get("sympy")
94+
if sympy is None:
9395
return False
96+
if isinstance(var, (list, tuple)):
97+
return any(isinstance(x, sympy.Expr) for x in var)
98+
return isinstance(var, sympy.Expr)
9499

95100

96101
@overload
@@ -122,7 +127,7 @@ def sin(theta):
122127
:seealso: :func:`sympy.sin`
123128
"""
124129
if issymbol(theta):
125-
return sympy.sin(theta)
130+
return _sympy().sin(theta)
126131
else:
127132
return math.sin(theta)
128133

@@ -156,7 +161,7 @@ def cos(theta):
156161
:seealso: :func:`sympy.cos`
157162
"""
158163
if issymbol(theta):
159-
return sympy.cos(theta)
164+
return _sympy().cos(theta)
160165
else:
161166
return math.cos(theta)
162167

@@ -190,7 +195,7 @@ def tan(theta):
190195
:seealso: :func:`sympy.cos`
191196
"""
192197
if issymbol(theta):
193-
return sympy.tan(theta)
198+
return _sympy().tan(theta)
194199
else:
195200
return math.tan(theta)
196201

@@ -224,7 +229,7 @@ def sqrt(v):
224229
:seealso: :func:`sympy.sqrt`
225230
"""
226231
if issymbol(v):
227-
return sympy.sqrt(v)
232+
return _sympy().sqrt(v)
228233
else:
229234
return math.sqrt(v)
230235

@@ -245,7 +250,7 @@ def zero() -> Symbol:
245250
246251
:seealso: :func:`sympy.S.Zero`
247252
"""
248-
return sympy.S.Zero
253+
return _sympy().S.Zero
249254

250255

251256
def one() -> Symbol:
@@ -264,7 +269,7 @@ def one() -> Symbol:
264269
265270
:seealso: :func:`sympy.S.One`
266271
"""
267-
return sympy.S.One
272+
return _sympy().S.One
268273

269274

270275
def negative_one() -> Symbol:
@@ -283,26 +288,27 @@ def negative_one() -> Symbol:
283288
284289
:seealso: :func:`sympy.S.NegativeOne`
285290
"""
286-
return sympy.S.NegativeOne
291+
return _sympy().S.NegativeOne
287292

288293

289294
def pi() -> Symbol:
290295
"""
291-
Symbolic constant: pi
296+
Symbolic constant: pi
292297
293-
:return: π
294-
:rtype: symbolic
298+
:return: π
299+
:rtype: symbolic
295300
296-
.. runblock:: pycon
301+
.. runblock:: pycon
297302
298-
>>> from spatialmath.base.symbolic import *
299-
>>> import math
300-
>>> sin(pi())
301-
>>> sin(math.pi)
303+
>>> from spatialmath.base.symbolic import *
304+
>>> import math
305+
from typing import TYPE_CHECKING
306+
>>> sin(pi())
307+
>>> sin(math.pi)
302308
303-
:seealso: :func:`sympy.S.Pi`
309+
:seealso: :func:`sympy.S.Pi`
304310
"""
305-
return sympy.S.Pi
311+
return _sympy().S.Pi
306312

307313

308314
def simplify(x: Symbol) -> Symbol:
@@ -324,10 +330,11 @@ def simplify(x: Symbol) -> Symbol:
324330
325331
:seealso: :func:`sympy.simplify`
326332
"""
327-
if _symbolics:
328-
return sympy.simplify(x)
329-
else:
333+
try:
334+
sympy = _sympy()
335+
except ImportError:
330336
return x
337+
return sympy.simplify(x)
331338

332339

333340
def det(x):
@@ -352,4 +359,4 @@ def det(x):
352359
.. note:: Converts to a SymPy ``Matrix`` and then back again.
353360
"""
354361

355-
return sympy.Matrix(x).det()
362+
return _sympy().Matrix(x).det()

0 commit comments

Comments
 (0)