# Part of Spatial Math Toolbox for Python
# Copyright (c) 2000 Peter Corke
# MIT Licence, see details in top-level file: LICENCE
"""
This package provides a light-weight wrapper to support use of SymPy. It
generalizes some common functions so that they can accept numerical or
Symbolic arguments.
If SymPy is not installed then only the standard numeric operations are
supported.
"""
import math
from spatialmath.base.types import *
try: # pragma: no cover
# print('Using SymPy')
import sympy
_symbolics = True
symtype = (sympy.Expr,)
from sympy import Symbol
except ImportError: # pragma: no cover
# SymPy is not installed
_symbolics = False
symtype = ()
Symbol = Any
# ---------------------------------------------------------------------------------------#
if _symbolics:
[docs] def symbol(
name: str, real: Optional[bool] = True
) -> Union[Symbol, Tuple[Symbol, ...]]:
"""
Create symbolic variables
:param name: symbol names
:type name: str
:param real: assume variable is real, defaults to True
:type real: bool, optional
:return: SymPy symbols
:rtype: sympy
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> theta = symbol('theta')
>>> theta
>>> theta, psi = symbol('theta psi')
>>> theta
>>> psi
>>> q = symbol('q_:6')
>>> q
.. note:: In Jupyter symbols are pretty printed.
- symbols named after greek letters will appear as greek letters
- underscore means subscript as it does in LaTex, so the symbols ``q``
above will be subscripted.
:seealso: :func:`sympy.symbols`
"""
return sympy.symbols(name, real=real)
[docs]def issymbol(var: Any) -> bool:
"""
Test if variable is symbolic
:param var: variable to test
:return: whether variable is symbolic
:rtype: bool
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> theta = symbol('theta')
>>> issymbol(theta)
>>> issymbol(3.4)
"""
if _symbolics:
if isinstance(var, (list, tuple)):
return any([isinstance(x, symtype) for x in var])
else:
return isinstance(var, symtype)
else:
return False
@overload
def sin(theta: float) -> float:
...
@overload
def sin(theta: Symbol) -> Symbol:
...
[docs]def sin(theta):
"""
Generalized sine function
:param θ: argument
:type θ: float or symbolic
:return: sin(θ)
:rtype: float or symbolic
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> theta = symbol('theta')
>>> sin(theta)
>>> sin(0.5)
:seealso: :func:`sympy.sin`
"""
if issymbol(theta):
return sympy.sin(theta)
else:
return math.sin(theta)
@overload
def cos(theta: float) -> float:
...
@overload
def cos(theta: Symbol) -> Symbol:
...
[docs]def cos(theta):
"""
Generalized cosine function
:param θ: argument
:type θ: float or symbolic
:return: cos(θ)
:rtype: float or symbolic
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> theta = symbol('theta')
>>> cos(theta)
>>> cos(0.5)
:seealso: :func:`sympy.cos`
"""
if issymbol(theta):
return sympy.cos(theta)
else:
return math.cos(theta)
@overload
def tan(theta: float) -> float:
...
@overload
def tan(theta: Symbol) -> Symbol:
...
[docs]def tan(theta):
"""
Generalized tangent function
:param θ: argument
:type θ: float or symbolic
:return: tan(θ)
:rtype: float or symbolic
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> theta = symbol('theta')
>>> tan(theta)
>>> tan(0.5)
:seealso: :func:`sympy.cos`
"""
if issymbol(theta):
return sympy.tan(theta)
else:
return math.tan(theta)
@overload
def sqrt(theta: float) -> float:
...
@overload
def sqrt(theta: Symbol) -> Symbol:
...
[docs]def sqrt(v):
"""
Generalized sqrt function
:param v: argument
:type v: float or symbolic
:return: √ v
:rtype: float or symbolic
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> x = symbol('x')
>>> sqrt(x ** 2)
>>> sqrt(4)
:seealso: :func:`sympy.sqrt`
"""
if issymbol(v):
return sympy.sqrt(v)
else:
return math.sqrt(v)
[docs]def zero() -> Symbol:
"""
Symbolic constant: zero
:return: 0
:rtype: symbolic
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> x = symbol('x')
>>> zero()
>>> x + zero()
:seealso: :func:`sympy.S.Zero`
"""
return sympy.S.Zero
[docs]def one() -> Symbol:
"""
Symbolic constant: one
:return: 1
:rtype: symbolic
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> x = symbol('x')
>>> one()
>>> one() * x
:seealso: :func:`sympy.S.One`
"""
return sympy.S.One
[docs]def negative_one() -> Symbol:
"""
Symbolic constant: negative one
:return: -1
:rtype: symbolic
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> x = symbol('x')
>>> negative_one()
>>> negative_one() * x
:seealso: :func:`sympy.S.NegativeOne`
"""
return sympy.S.NegativeOne
[docs]def pi() -> Symbol:
"""
Symbolic constant: pi
:return: π
:rtype: symbolic
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> import math
>>> sin(pi())
>>> sin(math.pi)
:seealso: :func:`sympy.S.Pi`
"""
return sympy.S.Pi
[docs]def simplify(x: Symbol) -> Symbol:
"""
Symbolic simplification
:param x: expression to simplify
:type x: symbolic
:return: -1
:rtype: symbolic
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> x = symbol('x')
>>> y = (x - 1) * (x + 1) - x ** 2
>>> y
>>> simplify(y)
:seealso: :func:`sympy.simplify`
"""
if _symbolics:
return sympy.simplify(x)
else:
return x
[docs]def det(x):
"""
Symbolic determinant
:param m: matrix
:type x: ndarray with symbolic elements
:return: determinant
:rtype: ndarray with symbolic elements
.. runblock:: pycon
>>> from spatialmath.base.symbolic import *
>>> from spatialmath.base import rot2
>>> theta = symbol('theta')
>>> R = rot2(theta)
>>> print(R)
>>> print(det(R))
>>> simplify(print(det(R)))
.. note:: Converts to a SymPy ``Matrix`` and then back again.
"""
return sympy.Matrix(x).det()