# Create named function from an expression

This is not implemented inside `fun_expr`. Sometimes it is useful to have named functions that know about their name and can use this

Initialisations:


```python
from sympy import *
init_printing()

import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format='retina'

import numpy as np

from IPython.display import display, Math, display_latex, Latex

from fun_expr import Function_from_Expression as FE, Named_Function_from_Expression as NFE
```

To create named functions, subclass FE:


```python
class Named_Function_from_Expression(FE):
    """
    Create a named function from an expression.
    
    Example fo use:
    >>> from sympy import *
    >>> from fun_expr import (
            Function_from_Expression as FE,
            Named_Function_from_Expression as NFE,
            )
    >>> f = NFE("f", x, x**2)
    >>> f.displ()
    '$f(x) = x^{2}$'
    >>> f_1 = f.diff(x,name="f'")
    >>> f_1.displ()
    "$f'(x) = 2 x$"
    >>> f_1 = f.diff(x)
    '((x) \mapsto 2\,x)'
    >>> print(type(f_1))
    fun_expr.function_from_expression.Function_from_Expression
    >>> f.displ(2)
    '$f(2) = 4$'
    """
    
    def __new__(cls, name, variables, expr):
        obj = FE.__new__(cls, variables, expr)
        obj.name = name
        return obj
    
    def displ(self, *values, par="$", n=False, simplify=False, **kwargs):
        """
        This is a convenience function to display the equation 
        
          $f(x_1,x_2,...) = rhs$
          or 
          $$f(x_1,x_2,...) = rhs$$ # if par='$$'
          
        in latex-notation. 

        If values == (): replace rhs by self.expr
        instead. If values are specified, exactly one 
        value for every variable of this function is 
        expected.
        
        If n ist True, replace the rhs by rhs.n().
        If n is an integer number replace the rhs by rhs.n(n).
        
        If simplify is true, simplify rhs.
        
        Here rhs always is the right hand side of the equation.
        """
        
        # there are exactly two possibilities:
        #    either there is no value at all, values == ()
        #    or there are exactly len(self.variables) values.
        #    if there are no values, set rhs to self.expr
        #    else calculate rhs = f(*values)
        if values is not ():
            vals = values 
            rhs = super().__call__(*values, **kwargs) 
        else:
            vals = self.variables
            rhs = self.expr
        
        # n can be None, bool or int.
        # if n is None do nothing
        # if n is bool and n is True, replace rhs by rhs.n()
        # if n is bool and n is False, do nothing
        # if n is int replace rhs by rhs.n(n)
        if n is not None:
            if isinstance(n, bool):
                if n:
                    rhs = rhs.n()
            else:
                rhs = rhs.n(n)
        
        # simplify can be True or False
        # if simplfiy ist True, the rhs is simplified
        if simplify:
            rhs = rhs.simplify()
        
        # create output as 
        #    f(x_1,x_2,...) = rhs
        # in latex notation
        ret_val = par 
        ret_val += latex(self.name)
        ret_val += r"\left({vars}\right) = ".format(vars=','.join(latex(v) for v in vals))
        ret_val += latex(rhs) 
        ret_val += par
        return ret_val
    
    def diff(self, *symbols, **assumptions):
        """
        If a name is specified, as_function is set to True.
        In this case, a Named_Function_of_Expression is retured.
        If no name is specified, the result depends on as_function
        """
        name = assumptions.pop('name',None)
        as_function = assumptions.pop('as_function', True) or name is not None 
        # let my parent do the hard part 
        res = super().diff(*symbols, as_function=as_function, **assumptions)
        if name is not None:
            return Named_Function_from_Expression(name, res.variables, res.expr)
        else:
            return res
        
    def integrate(self, *args, **kwargs):
        """
        If a name is specified, as_function is set to True.
        In this case, a Named_Function_of_Expression is returned.
        If no name is specified, the result depends on as_function
        """
        name = kwargs.pop('name',None)
        as_function = kwargs.pop('as_function', False) or name is not None
        # let my parent do the hard part
        res = super().integrate(*args, as_function=as_function, **kwargs)
        if name is not None:
            return Named_Function_from_Expression(name, res.variables, res.expr)
        else:
            return res
    
    def subs(self, *args, **kwargs):
        obj = Named_Function_from_Expression(self.name, self.variables, self.expr.subs(*args, **kwargs))
        if False in [u==v for u,v in zip(self.variables,obj.variables)]:
            raise ValueError(
                    "Substitution of variables is not valid: {}".format(arg) 
                    )
        return obj
```


```python
a,x = symbols('a,x')
f = Named_Function_from_Expression('f', x, a*x**2)
Math(f.displ())
#f = FE(x,a*x**2)
```




$$f\left(x\right) = a x^{2}$$




```python
display(f.subs(x,1))
display(type(f))
```


```python
f = Named_Function_from_Expression('f',(a,x),a*x**2)
f
```


```python
f.subs(a,x)
```


```python
f = Lambda((a,x), a*x**2)
f
```


```python
f.subs(a,x)
```


```python
a_0 = Symbol('a_0')
f.subs(a,a_0)
```


```python
f = Named_Function_from_Expression('f', (a,x), a*x)
Math(f.displ())
```




$$f\left(a,x\right) = a x$$




```python
f.subs(a,a_0)
```


```python
class A():
    name = 'A'
    
    def ret_name(self):
        return (A.name, self.name)
    
class B(A):
    name = 'B'
    
    def ret_name_too(self):
        return super().ret_name()
    
```


```python
b = B()
print(b.ret_name())
b.ret_name_too()
```

    ('A', 'B')





    ('A', 'B')




```python
a = A()
print(a.ret_name())
```

    ('A', 'A')



```python
a,x = symbols('a,x')
g = Lambda(x,a*x**2)
g
```


```python
g.subs(a,1)
```


```python
g.subs(a,1)
```


```python
f = NFE('f', x, a*x**2)
f.displ()
```




    '$f\\left(x\\right) = a x^{2}$'




```python
f.subs({a:1})
```

Create a simple function to test `Named_Function_from_Expression`:


```python
x = Symbol('x')
f = Named_Function_from_Expression("f",x,x**2)
f.displ()
```


```python
x = Symbol('x')
f = NFE('f', x, x**2)
display(f.displ())
type(f)
```


```python
f.displ(par='$$')
```


```python
# to have nice output:
Math(f.displ())
```


```python
display(f.displ(2))
Math(f.displ(2))
```

Plot the graph $y=f(x)$ for $-1 \le x \le 1$:


```python
fig, ax = plt.subplots()

lx = np.linspace(-1,1)

ax.plot(lx,f.lambdified(lx),label=f.displ())
ax.axhline(0,c='k')
ax.axvline(0,c='k')
ax.grid(True)
ax.legend(loc='upper center')
ax.set_title("Graph of the square function {f}".format(f=f.displ()))
```

Determine the derivative of $f(x)$ as named function:


```python
f_1 = f.diff(x,name="f'")
display(f_1.displ())
display(Math(f_1.displ()))
display(type(f_1))
```

If no name is specified for `f.diff(x)`, a function of class `FE` is returned:


```python
f_1 = f.diff(x)
display(f_1)
display(type(f_1))
```

If no name is specified for `f.diff(x)` and `is_function=False`, an expression is returned:


```python
expr = f.diff(x,as_function=False)
display(expr)
type(expr)
```

Integration leads to an expression, not to a function:


```python
f_0 = f.integrate(x)
f_0
```

If `as_function=True` is set, the result is of type `FE`:


```python
f_0 = f.integrate(x,as_function=True)
display(f_0)
display(type(f_0))
```

If a name is specified `as_function` is set to `True`, the result is of type `Named_Function_from_Expression`:


```python
f_0 = f.integrate(x,name="f_0")
display(Math(f_0.displ()))
display(type(f_0))
```


```python
f_0 = f.integrate(x,name='f_0',as_function=False)
display(Math(f_0.displ()))
type(f_0)
```


```python
Math(f_0.displ(3))
```

Named functions of more then one variable are possible, too.


```python
a,x,y,z = symbols('a,x,y,z')

g = NFE('g',(x,y,z), a*x*y*z)
Math(g.displ())
```


```python
Math(g.diff(z,name=r"\frac{\partial}{\partial z}\,g").displ())
```

Names of functions can be latex expressions like `r'\mu'`:


```python
mu = NFE(r'\mu', (x,y), x+y)
display(mu.displ())
Math(mu.displ())
```

Evaluation of a function works the expected way:


```python
display(mu(x/y,y/x))
Math(mu.displ(x/y,y/x))
```

Evaluation of a function needs exactly one value for every variable:


```python
try:
    mu(1)
except TypeError as e:
    print("Error: " + str(e))
```


```python
try:
    mu.displ(1)
except TypeError as e:
    print("Error: " + str(e))
```


```python
try:
    mu.displ(1,2,3)
except TypeError as e:
    print("Error: " + str(e))
```


```python
try:
    mu.displ(1,2,3)
except TypeError as e:
    print("Error: " + str(e))
```

829c1efae8980ebccd85d41736a0ce816ae87233
ipynb
Jupyter Notebook
docs/create_named_function_from_expression.ipynb
w-meiners/fun-expr
a44f0366f08c8c2d2eb2702176698bfe3f6febed
MIT
2017-12-20T16:16:40.000Z
2022-02-28T11:06:38.000Z
docs/create_named_function_from_expression.ipynb
w-meiners/fun-expr
a44f0366f08c8c2d2eb2702176698bfe3f6febed
MIT
docs/create_named_function_from_expression.ipynb
w-meiners/fun-expr
a44f0366f08c8c2d2eb2702176698bfe3f6febed
MIT
2020-01-27T09:50:59.000Z
2020-01-27T09:50:59.000Z
Qwen/Qwen-72B
1. YES
2. YES