1111supported.
1212"""
1313
14+ from __future__ import annotations
15+
1416import math
17+ from typing import TYPE_CHECKING
1518from 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
5956
60- .. note:: In Jupyter symbols are pretty printed.
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
6165
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.
66+ .. note:: In Jupyter symbols are pretty printed.
6567
66- :seealso: :func:`sympy.symbols`
67- """
68- return sympy .symbols (name , real = real )
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.
71+
72+ :seealso: :func:`sympy.symbols`
73+ """
74+ return _sympy ().symbols (name , real = real )
6975
7076
7177def 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
251256def 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
270275def negative_one () -> Symbol :
@@ -283,7 +288,7 @@ def negative_one() -> Symbol:
283288
284289 :seealso: :func:`sympy.S.NegativeOne`
285290 """
286- return sympy .S .NegativeOne
291+ return _sympy () .S .NegativeOne
287292
288293
289294def pi () -> Symbol :
@@ -302,7 +307,7 @@ def pi() -> Symbol:
302307
303308 :seealso: :func:`sympy.S.Pi`
304309 """
305- return sympy .S .Pi
310+ return _sympy () .S .Pi
306311
307312
308313def simplify (x : Symbol ) -> Symbol :
@@ -324,10 +329,11 @@ def simplify(x: Symbol) -> Symbol:
324329
325330 :seealso: :func:`sympy.simplify`
326331 """
327- if _symbolics :
328- return sympy . simplify ( x )
329- else :
332+ try :
333+ sympy = _sympy ( )
334+ except ImportError :
330335 return x
336+ return sympy .simplify (x )
331337
332338
333339def det (x ):
@@ -352,4 +358,4 @@ def det(x):
352358 .. note:: Converts to a SymPy ``Matrix`` and then back again.
353359 """
354360
355- return sympy .Matrix (x ).det ()
361+ return _sympy () .Matrix (x ).det ()
0 commit comments