Domain-specific functions that differentiate, simplify and print correctly make SymPy fit your field.
Function("f") creates an undefined function with no properties. To give a function real behaviour, subclass sympy.Function and override hooks:
eval(cls, *args)(a classmethod) runs on construction. Return a simplified value for special inputs, for example a number for numeric arguments, orNoneto leave the call unevaluated. Keep it cheap: it runs every time the function is created.fdiff(self, argindex=1)returns the derivative with respect to the given argument (1-based). With it,diff, the chain rule andserieswork automatically._eval_is_real,_eval_is_positiveand similar methods answer assumption queries, returningTrue,FalseorNone._eval_evalf(self, prec)provides numeric evaluation, and_eval_rewrite_as_<Name>enablesexpr.rewrite(Name).- Printing is customised with methods such as
_latex(self, printer).
Arguments are always sympified, so inside eval you work with SymPy objects: test numbers with arg.is_Number or arg.is_zero, not with isinstance(arg, int).
The design rule is: automatic evaluation in eval should only do things that are always correct and cheap. Expensive or context-dependent rewrites belong in methods users call explicitly, such as doit, rewrite or an _eval_simplify hook, which is also how built-in functions keep construction fast.
Example
from sympy import Function, symbols, diff, exp, series, S
class softplus(Function):
"""softplus(x) = log(1 + exp(x))."""
@classmethod
def eval(cls, x):
if x.is_zero:
return S("log(2)")
return None # stay unevaluated
def fdiff(self, argindex=1):
x = self.args[0]
return 1 / (1 + exp(-x)) # the logistic sigmoid
def _eval_is_positive(self):
return self.args[0].is_extended_real
x = symbols("x", real=True)
print(softplus(0)) # log(2)
print(diff(softplus(x**2), x)) # 2*x/(1 + exp(-x**2))
print(softplus(x).is_positive) # True
print(series(softplus(x), x, 0, 3)) # log(2) + x/2 + x**2/8 + O(x**3)Remember
- Subclass Function; eval runs on construction and returns None to stay unevaluated
- fdiff makes diff, the chain rule and series work
- _eval_is_* methods plug into the assumptions system
Watch out for
- Doing expensive simplification inside eval, which slows every construction.