# Adding functions y(x) together in python – impossible?

I’m currently trying to solve an engineering problem where I need to solve relatively long differential equations, for which I’m using the method odeint from scipy. I changed my problem to more easy variables and equations to shorten the code below and make it clearer.

I want to add two parts of a function, here `f` and `g`, to build my final equation `dydt`. Both parts have the same variables `y,x` and other different constant arguments.

I get the error “unsupported operand type(s) for +: ‘function’ and ‘function'”.

Is there a way around this? I assume I could try to use `lambda` but I’m fairly new to python, and couldn’t figure a way to make it work.

```from scipy.integrate import odeint

def f(y, x, arg1, arg2):
f_result = y + x + arg1 * arg2
return f_result

def g(y, x, arg3, arg4, arg5):
g_result = y * x * (arg3 + arg4 + arg5)
return g_result

def equation(fun1, fun2):
dydt = fun1 + fun2
return dydt

y0, x_span, arg1, arg2, arg3, arg4, arg5 = 0, [x for x in range(11)] , 1, 2, 3, 4, 5

f_fun = f
g_fun = g
dydt = equation(f_fun, g_fun)

sol = odeint(dydt, y0, x_span, args=(arg1, arg2, arg3, arg4, arg5))
```

You can build that kind of stuff, but it will not be very easy to maintain. Your problem here is that you want to return a function from a function. You can do this easily in python because you can define a function inside one, and return it.

Where it gets hard is that your “inner” functions will require different arguments every time, so managing to build dynamically another function that will have the right signature is… Well, as you will see, it’s not that hard, it will just not necessarily be the easiest to maintain.

A very raw way to do what you want is this:

```from scipy.integrate import odeint

def f(y, x, arg1, arg2):
f_result = y + x + arg1 * arg2
return f_result

def g(y, x, arg3, arg4, arg5):
g_result = y * x * (arg3 + arg4 + arg5)
return g_result

def equation(fun1, fun2):
def inner(*args):
y = args[0]
x = args[1]
arg1 = args[2]
arg2 = args[3]
arg3 = args[4]
arg4 = args[5]
arg5 = args[6]
dydt = fun1(x, y, arg1, arg2) + fun2(x, y, arg3, arg4, arg5)
return dydt
return inner

y0, x_span, arg1, arg2, arg3, arg4, arg5 = 0, [x for x in range(11)], 1, 2, 3, 4, 5

f_fun = f
g_fun = g
dydt = equation(f_fun, g_fun)

sol = odeint(dydt, y0, x_span, args=(arg1, arg2, arg3, arg4, arg5))
```

`inner` takes `*args` as arguments, which means “any number of arguments, as a list”. As you can see i precised how to extract each value from it to rebuild our function.

Here, this is really raw and straightforward. If you want to actually make it a bit more permissive, you can try to guess the number of args your functions `f` and `g` require by inspecting them, so f and g can change a bit.

warning: using some relatively advanced techniques from now on.Mostly argument unpacking

```from scipy.integrate import odeint
from inspect import signature

def f(y, x, arg1, arg2):
f_result = y + x + arg1 * arg2
return f_result

def g(y, x, arg3, arg4, arg5):
g_result = y * x * (arg3 + arg4 + arg5)
return g_result

def equation(fun1, fun2):
def inner(*args):
y = args[0]
x = args[1]
fun_args = (x for x in list(args[2:]))
fun1_args = [next(fun_args) for _ in range(len(signature(fun1).parameters) - 2)] # We remove 2 because y and x are always there
fun2_args = [next(fun_args) for _ in range(len(signature(fun2).parameters) - 2)]
dydt = fun1(x, y, *fun1_args) + fun2(x, y, *fun2_args)
return dydt
return inner

y0, x_span, arg1, arg2, arg3, arg4, arg5 = 0, [x for x in range(11)], 1, 2, 3, 4, 5

f_fun = f
g_fun = g
dydt = equation(f_fun, g_fun)

sol = odeint(dydt, y0, x_span, args=(arg1, arg2, arg3, arg4, arg5))
```

And for good measure, you could even decide to do an arbitrary number of functions, as long as you always put the parameters in the right order!

```from scipy.integrate import odeint
from inspect import signature

def f(y, x, arg1, arg2):
f_result = y + x + arg1 * arg2
return f_result

def g(y, x, arg3, arg4, arg5):
g_result = y * x * (arg3 + arg4 + arg5)
return g_result

def equation(*args):
def inner(*inner_args):
y = inner_args[0]
x = inner_args[1]
dydt = []
fun_args = (x for x in list(inner_args[2:]))
for func in args:
func_args = [next(fun_args) for _ in range(len(signature(func).parameters) - 2)] # We remove 2 because y and x are always there
dydt.extend(func(x, y, *func_args))
return sum(dydt)
return inner

y0, x_span, arg1, arg2, arg3, arg4, arg5 = 0, [x for x in range(11)], 1, 2, 3, 4, 5

f_fun = f
g_fun = g
dydt = equation(f_fun, g_fun)

sol = odeint(dydt, y0, x_span, args=(arg1, arg2, arg3, arg4, arg5))
```

The real question now is, is it really more maintainable that way?

Here’s how i’d do it, personnally, allowing for some quick modifications witout it being too complicated.

```def equation(y, x, *fun_args):
def f():
arg1, arg2 = *fun_args[0]
f_result = y + x + arg1 * arg2
return f_result

def g():
arg3, arg4, arg5 = *fun_args[1]
g_result = y * x * (arg3 + arg4 + arg5)
return g_result

return f() + g()

y0, x_span, arg1, arg2, arg3, arg4, arg5 = 0, [x for x in range(11)], 1, 2, 3, 4, 5

sol = odeint(equation, y0, x_span, args=((arg1, arg2), (arg3, arg4, arg5)))
```

This allows me only to play with the inner part of the “equation” function, as long as i pass the correct args in `sol = odeint(equation, y0, x_span, args=((arg1, arg2), (arg3, arg4, arg5)))`. I also regrouped function parameters in tuple for better visiblity.

User contributions licensed under: CC BY-SA
8 People found this is helpful