Python: elegant way to use container in lambdified sympy expression - python

Say I have a container class containing some numeric value:
class Container:
def __init__(self, value):
self.value = value
I would like to give those Containers to a lambdified sympy expression and as a result get a new Container:
import sympy as sp
a = Container(1)
b = Container(-0.5)
x, y = sp.symbols("x y")
f = sp.lambdify((x, y), sp.atan(x)**-1*sp.pi*10 - 1/y)
result = f(a, b) # result should be a Container with value 42
Now I actually don't mind writing some wrapper around f that unpacks and packs the values, but the problem occurs here:
Like in Numpy, I also want it to be able to handle lists or lists mixed with values:
a = Container(-47)
b = Container(19)
c = [Container(0), Container(1), Container(2), Container(3), Container(4)]
x, y, z = sp.symbols("x y z")
f = sp.lambdify((x, y, z), z**4 + x/6*z**3 + y*z**2 + (x-2*y)/6*z + 3)
result = f(a, b, c) # should be a list of Containers with values 3, 1, 4, 1 and 5
What is an elegant way to achieve this?
Edit
I already wrote a wrapper function around lambdify that unpacks the values before applying the sympy expression and packs up the result, but it involves checking whether the type of an argument is a list or a Container, which is against the whole duck-typing philosophy of Python. My actual question: is there a more pythonic way to do this that doesn't involve ugly type-checking?

Related

In sympy, evaluating an expression using .subs() when that expression has been defined in another function

If I define a symbolic expression inside a function, how do I evaluate that expression using .subs() outside that function. The following code demonstrates the issue:
import sympy as sp
def myFun():
x = sp.symbols("x", positive = True)
y = 3*x
return y
expr = myFun()
print(expr.subs({x:10}))
Running the code above gives NameError: name 'x' is not defined.
How can I perform this evaluation? Two things I could think of is declaring x to be a global variable using global x inside myFun(). However, I want to avoid this method. Another way is to lambdify the expression in myFun() before returning the expression. But I would prefer not to use this method because in the real problem I am working with, I have multiple variables in my expression and would like to substitute only some of the variables at one stage, and the remaining variables at a later stage. Would you adopt one of the two approaches I mentioned or is it possible to evaluate the expression using .subs() using some other approach?
#cards answer is not going to work, the reason is that you have defined x to be positive, instead when calling print(expr.subs({'x':10})) the string 'x' will generate a generic symbol, without assumptions.
You either create your symbols outside of the function, like this:
import sympy as sp
x = sp.symbols("x", positive = True)
def myFun():
y = 3*x
return y
expr = myFun()
print(expr.subs({x:10}))
# out: 30
Or you can retrieve the symbols that make up a symbolic expression with the free_symbol attribute, like this:
import sympy as sp
def myFun():
x = sp.symbols("x", positive = True)
y = 3*x
return y
expr = myFun()
x = expr.free_symbols.pop()
print(expr.subs({x:10}))
# out: 30
EDIT (to accommodate comment):
I was just wondering but what if the expression had three variables, say 5*y + 3*x + 7*z? I tried the code you provided. The line expr.free_symbols.pop() only gives one of the variables - it gives x. Is there a way to use free_symbols to get all three variables?
free_symbols returns a set with all variables. If the expression is expr = 5*y + 3*x + 7*z, then expr.free_symbols returns {x, y, z}.
pop() is a method of Python's set: it returns the first element of a set.
To get all the variables of your expression you could try: x, y, z = expr.free_symbols, or x, y, z = list(expr.free_symbols). However, this creates the following problem: execute print(x, y, z) and you'll get something similar to: y z x. In other words, the symbols returned by expr.free_symbols are unordered. This is the default Python behavior.
Can we get ordered symbols? Yes, we can sort them alphabetically with : x, y, z = sorted(expr.free_symbols, key=str). If we now execute print(x, y, z) we will get x y z.
Let's create an expression with two symbols in it:
In [44]: x, y = symbols('x, y')
In [45]: expr = sin(y) + x**2
Suppose that we only have the expression as a local variable. We can find the symbols it contains using free_symbols. We can use that to build a dict that maps symbol names (as strings) to the symbols themselves:
In [46]: expr.free_symbols
Out[46]: {x, y}
In [47]: syms = {s.name: s for s in expr.free_symbols}
In [48]: syms
Out[48]: {'y': y, 'x': x}
This mapping is important because different symbols with the same names but different assumptions are not interchangeable. With that we can substitute the symbol whose name is 'x' like this:
In [49]: expr.subs(syms['x'], 2)
Out[49]: sin(y) + 4
x as a Symbol object is only declared in the function's body, so it's not accessible from the outside. Use 'x' as a string.
x = list(expr.free_symbols)[0]
print(expr.subs({x: 2}))

iterating functions definitions, based on a parent function

I am trying to write some code to perform a statistical test called model-reduction. Basically what I want to know is whether each variable in my function makes a meaningful contribution (i.e. significantly explains variance). Say for example my original fit-function looks like this:
full_model(x, a, b, c, d):
return a + b*x + c*x**3 + sin(d*x)
I want to compare reduced forms of this model. The once I need to check are:
reduced = lambda x, b, c, d: full_model(x, 0, b, c, d)
reduced = lambda x, a, c, d: full_model(x, a, 0, c, d)
reduced = lambda x, a, b, d: full_model(x, a, b, 0, d)
reduced = lambda x, a, b, c: full_model(x, a, b, c, 0)
For each case, I run some sort of test that I don't go into detail:
compare_models(full_model, reduced, x, y)
In reality, my fit function has more parameters, and I want test even further reduced functions. The code will be really messy if I have to explicitly define all possible models. Is there any way to define the reduced function in a for-loop? And is there any existing python module that can achieve what I want to do?
I would harness functools.partial for that following way, consider following simplified example:
import functools
def sum3(x, y, z):
return x+y+z
args = ["x", "y", "z"]
red_dict = {}
for arg in args:
red_dict[arg] = functools.partial(sum3, **{arg: 0})
print(red_dict["x"](y=10,z=10))
print(red_dict["y"](x=10,z=10))
print(red_dict["z"](x=10,y=10))
Output:
20
20
20
Explanation: args is list of args names you want to zero, in for-loop I use argument unpacking (**) to fix selected argument value to zero, then I store result in red_dict. Use loop is equivalent to doing:
red_dict["x"] = functools.partial(sum3, x=0)
red_dict["y"] = functools.partial(sum3, y=0)
red_dict["z"] = functools.partial(sum3, z=0)

Python- np.random.choice

I am using the numpy.random.choice module to generate an 'array' of choices based on an array of functions:
def f(x):
return np.sin(x)
def g(x):
return np.cos(x)
base=[f, g]
funcs=np.random.choice(base,size=2)
This code will produce an 'array' of 2 items referencing a function from the base array.
The reason for this post is, I have printed the outcome of funcs and recieved:
[<function f at 0x00000225AC94F0D0> <function f at 0x00000225AC94F0D0>]
Clearly this returns a reference to the functions in some form, not that I understand what that form is or how to manipulate it, this is where the problem comes in. I want to change the choice of function, so that it is no longer random and instead depends on some conditions, so it might be:
for i in range(2):
if testvar=='true':
choice[i] = 0
if testvar== 'false':
choice[i] = 1
This would return an array of indicies to be put in later function
The problem is, the further operations of the code (I think) require this previous form of function reference: [ ] as an input, instead of a simple array of 0,1 Indicies and I don't know how I can get an array of form [ ] by using if statements.
I could be completely wrong about the rest of the code requiring this input, but I don't know how I can amend it, so am hence posting it here. The full code is as follows: (it is a slight variation of code provided by #Attack68 on Evolving functions in python) It aims to store a function that is multiplied by a random function on each iteration and integrates accordingly. (I have put a comment on the code above the function that is causing the problem)
import numpy as np
import scipy.integrate as int
def f(x):
return np.sin(x)
def g(x):
return np.cos(x)
base = [f, g]
funcs = np.random.choice(base, size=2)
print(funcs)
#The below function is where I believe the [<function...>] input to be required
def apply(x, funcs):
y = 1
for func in funcs:
y *= func(x)
return y
print('function value at 1.5 ', apply(1.5, funcs))
answer = int.quad(apply, 1, 2, args=(funcs,))
print('integration over [1,2]: ', answer)
Here is my attempt of implementing a non-random event:
import numpy as np
import scipy.integrate as int
import random
def f(x):
return np.sin(x)
def g(x):
return np.cos(x)
base = [f, g]
funcs = list()
for i in range(2):
testvar=random.randint(0,100) #In my actual code, this would not be random but dependent on some other situation I have not accounted for here
if testvar>50:
func_idx = 0 # choose a np.random operation: 0=f, 1=g
else:
func_idx= 1
funcs.append(func_idx)
#funcs = np.random.choice(base, size=10)
print(funcs)
def apply(x, funcs):
y = 1
for func in funcs:
y *= func(x)
return y
print('function value at 1.5 ', apply(1.5, funcs))
answer = int.quad(apply, 1, 2, args=(funcs,))
print('integration over [1,2]: ', answer)
This returns the following error:
TypeError: 'int' object is not callable
If: You are trying to refactor your original code that operates on a list of randomly chosen functions to a version that operates with random indices which correspond to items in a list of functions. Refactor apply.
def apply(x,indices,base=base):
y = 1
for i in indices:
f = base[i]
y *= f(x)
return y
...this returns a reference to the functions in some form, not that I understand what that form is or how to manipulate it...
Functions are objects, the list contains a reference to the objects themselves. They can be used by either assigning them to a name then calling them or indexing the list and calling the object:
>>> def f():
... return 'f'
>>> def g():
... return 'g'
>>> a = [f,g]
>>> q = a[0]
>>> q()
'f'
>>> a[1]()
'g'
>>> for thing in a:
print(thing())
f
g
Or you can pass them around:
>>> def h(thing):
... return thing()
>>> h(a[1])
'g'
>>>
If you still want to use your function apply as-is, you need to keep your input a list of functions. Instead of providing a list of indices, you can use those indices to create your list of functions.
Instead of apply(1.5, funcs), try:
apply(1.5, [base(n) for n in funcs])

Using the lambda function for a function that depends on the input

As an input, I have a list of nonnegative whole numbers which are supposed to be the coefficients of a polynomial. But I also want to evaluate the polynomial for a certain number x.
For example:
If we have L=[2,3,1] as an input and x=42 we get 2x^2+3x+1=3655
What I want is for example:
>>>p=polynomial([2,3,1])
>>>p(O)
1
>>>p(42)
>>>3655
I guess I have to make use of the lambda function somehow, and I do know how it works for two variables on a given function, but in this case the function depends on my input.
def polynomial(coef):
coef=coef[::-1]
for i in range(len(coef)):
p=lambda x: coef[i]*x**i
p+=p
return lambda x: p
This is of course absolute nonsense, as I cannot add up one lambda function to another, but this is what my approaching "intuition" is.
Some hints are much appreciated.
The most obvious pythonic solution (using a closure - with a lambda or (preferably) a named inner function) has already been posted, but for the sake of completeness I'll add the other pythonic solution - the OO version using a custom callable class:
class Polynomial(object):
def __init__(self, coef):
self.coef = coef
def __call__(self, x):
n = len(self.coef) - 1
return sum(c * x ** (n - i) for (i, c) in enumerate(self.coef))
p = Polynomial([2,3,1])
print p(0)
print p(42)
Simple Python:
def make_poly(coefs):
def poly(x):
result = 0
for c in coefs:
result = result * x + c
return result
return poly
p = make_poly([2,3,1])
print(p(0))
print(p(42))
EDIT: code modified as suggested by Mark Dickinson in the comments
You can do it using lambda:
def polynomial(coef):
n = len(coef) - 1
return lambda x : sum([c * x ** (n - i) for (i, c) in enumerate(coef)])
Lambda isn't necessary however, you can define another function inside the polynomial function like so:
def polynomial(coef):
def f(x):
n = len(coef) - 1
return sum([c * x ** (n - i) for (i, c) in enumerate(coef)])
return f
Edit: Previously input was tied to 3 coefficients
The following lambda function evaluates a polynomial function, input as a coeff list of coefficients, in a given x:
from functools import reduce
lambda coeff, x: reduce(lambda a, b: a*x + b, coeff)
It seems that you want to generate these polynomial functions. You can still do it with this method:
def generate_polynomial(coeff):
return lambda x: (lambda y: reduce(lambda a, b: a*y + b, coeff))(x)
>>> p = generate_polynomial([20,0,17])
>>> p(10)
2017
This is merely based on Horner's algorithm.
Besides, if you want to use exclusively lambda and no built-in functions, you can also emulate reduce with lambda functions. You might want to give a look at Python - Removing duplicates in list only by using filter and lambda and Removing duplicates using only lambda functions, showing how to get rid of the filter function.

Returning intermediate results from function in Python

Imagine I've got a Python module with some function in it:
def sumvars(x, y, z):
s = x
s += y
s += z
return s
But sometimes I want to get results of some intermediate calculations (for example, I could have a function which reverses a matrix and would like to know the determinant which has been calculated as an intermediate step as well). Obviously, I wouldn't want to redo those calculations again if they were already done within that function.
My first idea is to return a dict:
def sumvars(x, y, z):
d = {}
s = x
d['first_step'] = s
s += y
d['second_step'] = s
s += z
d['final'] = s
return d
But I don't recall any functions in numpy or scipy which return dicts and so it seems like this might be not a good idea. (Why?) Also routinely I'll always have to type sumvars(x,y,z)['final'] for a default return value...
Another option I see is creating global variables but seems wrong having a bunch of them in my module, I would need to remember their names and in addition not being attached to the function itself looks like a bad design choice.
What would be the proper function design for such situation?
Generally when you have two different ways you want to return data, go ahead and make two different functions. "Flat is better than nested", after all. Just have one call the other so that you Don't Repeat Yourself.
For example, in the standard library, urllib.parse has parse_qs (which returns a dict) and parse_qsl (which returns a list). parse_qs just then calls the other:
def parse_qs(...):
parsed_result = {}
pairs = parse_qsl(qs, keep_blank_values, strict_parsing,
encoding=encoding, errors=errors)
for name, value in pairs:
if name in parsed_result:
parsed_result[name].append(value)
else:
parsed_result[name] = [value]
return parsed_result
Pretty straightforward. So in your example it seems fine to have
def sumvars(x, y, z):
return sumvars_with_intermediates(x, y, z).final
def sumvars_with_intermediates(x, y, z):
...
return my_namedtuple(final, first_step, second_step)
(I favor returning namedtuples instead of dicts from my APIs, it's just prettier)
Another obvious example is in re: re.findall is its own function, not some configuration flag to search.
Now, the standard library is a sprawling thing made by many authors, so you'll find counterexamples to every example. You'll far more often see the above pattern rather than one omnibus function that accepts some configuration flags, though, and I find it far more readable.
Put the common calculation into its own function as Jayanth Koushik recommended if that calculation can be named appropriately. If you want to return many values (an intermediate result and a final result) from a single function then a dict may be an overkill depending on what is your goal but in python it is much more natural to simply return a tuple if your function has many values to return:
def myfunc():
intermediate = 5
result = 6
return intermediate, result
# using the function:
intermediate, result = myfunc()
Not sure if function attributes is a good idea:
In [569]: def sumvars(x, y, z):
...: s = x
...: sumvars.first_step = s
...: s += y
...: sumvars.second_step = s
...: s += z
...: return s
In [570]: res=sumvars(1,2,3)
...: print res, sumvars.first_step, sumvars.second_step
...:
6 1 3
Note: as #BrenBarn mentioned, this idea is just like global variables, your previously calculated "intermediate results" could not be stored when you want to reuse them.
Just came up with this idea which could be a better solution:
def sumvars(x, y, z, mode = 'default'):
d = {}
s = x
d['first_step'] = s
s += y
d['second_step'] = s
s += z
d['final'] = s
if mode == 'default':
return s
else:
return d
I belive the proper solution is to use a class, to have a better grasp of what you are modeling. For example in the case of the Matrix, you could simply store the determinant in the "determinant" attribute.
Here is an example using your matrix example.
class Matrix:
determinant = 0
def calculate_determinant(self):
#calculations
return determinant
def some_method(self, args):
# some calculations here
self.determinant = self.calculate_determinant()
# other calculations
matrix = Matrix()
matrix.some_method(x, y, z)
print matrix.determinant
This also allows you to separate your method into simpler methods, like one for calculating the determinant of your matrix.
Another variation:
def sumvars(x, y, z, d=None):
s = x
if not d is None:
d['first_step'] = s
s += y
if not d is None:
d['second_step'] = s
s += z
return s
The function always returns the desired value without packing it into a tuple or dictionary. The intermediate results are still available, but only if requested. The call
sumvars(1, 2, 3)
just returns 6 without storing intermediate values. But the call
d = {}
sumvars(1, 2, 3, d)
returns the same answer 6 and inserts the intermediate calculations into the supplied dictionary.
Option 1. Make two separate functions.
Option 2. Use a generator:
>>> def my_func():
... yield 1
... yield 2
...
>>> result_gen = my_func()
>>> result_gen
<generator object my_func at 0x7f62a8449370>
>>> next(result_gen)
1
>>> next(result_gen)
2
>>> next(result_gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
Inspired by #zhangxaochen solution, here's my take on your problem using class attributes:
class MyClass():
def __init__(self):
self.i = 4
def f(self):
s = self.i
MyClass.first_step = s
print(MyClass.first_step)
s += self.i
MyClass.second_step = s
print(MyClass.second_step)
s += self.i
return s
def main():
x = MyClass()
print(x.f()) # print final s
print(x.first_step)
print(x.second_step)
print(MyClass.second_step)
Note: I included several prints to make it more explicit how attribute values can be retrieved.
Result:
4
8
12
4
8
8

Categories

Resources