SymPy lambdify with dot() - python

Take an undefined function that happens to be named dot, and make it part of lambdify:
import numpy
import sympy
class dot(sympy.Function):
pass
x = sympy.Symbol('x')
a = sympy.Matrix([1, 0, 0])
f = sympy.lambdify(x, dot(a.T, x))
x = numpy.array([3, 2, 1])
print(f(x))
Surprise: This actually works!
Apparently, the string "dot" is somehow extracted and replaced by an implementation of the dot-product. Does anyone know which?
The result of the above is [3]. I would, however, like to get the scalar 3. (How) can I modify f() to achieve that?

I'm not a sympy user however quoting the documentation for lambdify it says:
If not specified differently by the user, SymPy functions are replaced
as far as possible by either python-math, numpy (if available) or
mpmath functions - exactly in this order. To change this behavior, the
“modules” argument can be used. It accepts:
the strings “math”, “mpmath”, “numpy”, “numexpr”, “sympy”
any modules (e.g. math)
dictionaries that map names of sympy functions to arbitrary functions
lists that contain a mix of the arguments above, with higher priority given to entries appearing first.
So it seems that if you have python-math installed it will use that, if not but you have numpy installed it will use numpy's version, otherwise mpmat and then describes how to modify this behaviour.
In your case just provide a modules value that is a dictionary that maps the name dot to a function that return a scalar as you want.
An example of what I mean:
>>> import numpy as np
>>> import sympy
>>> class dot(sympy.Function): pass
...
>>> x = sympy.Symbol('x')
>>> a = sympy.Matrix([1,0,0])
>>> f = sympy.lambdify(x, dot(a.T, x), modules=[{'dot': lambda x, y: np.dot(x, y)[0]}, 'numpy'])
>>> y = np.array([3,2,1])
>>> print(f(y))
3
>>> print(type(f(y)))
<class 'numpy.int64'>
As you can see by manipulating the modules argument you can achieve what you want. My implementation here is absolutely naive, but you can generalize it like:
>>> def my_dot(x, y):
... res = np.dot(x, y)
... if res.ndim == 1 and res.size == 1:
... return res[0]
... return res
This function checks whether the result of the normal dot is a scalar, and if so returns the plain scalar and otherwise return the same result as np.dot.

Related

Sympy patterns for functions

In Wolfram Research Mathematica it is possible to define a sustitution rule of the type
sustitution = g_[arg___] -> g[Sequence ## Reverse[{arg}]]
Then it is possible to apply it to different expressions involving functions with the following results:
f[x, y] /. sustitution >> f[y,x]
g[x1,x2,x3] >> g[x3,x2,x1]
h[1,z,w,t]/.sustitution >> h[t,w,z,1]
As it is possible to use a pattern with name, g_, for the name of the function and another pattern, arg___, for the arguments, the same sustitution rule is valid no matter the name of the function that appears in the expression.
Is it possible to use WildFunction symbols along with replace to obtain a similar efect with Sympy?
The arg__ is not necessary for a SymPy type arg-remapping function since the args can be retrieved from the function call itself:
>>> from sympy.abc import x,y,z
>>> from sympy import Function
>>> f = Function('f')
>>> g_ = lambda f: f.func(*list(reversed(f.args)))
>>> g_(f(x,y,z))
f(z, y, x)
Another way of changing all user-defined functions the same way within an expression is to use replace as follows:
>>> from sympy.abc import *
>>> from sympy import Function, AppliedUndef
>>> f,g,h,F=symbols('f,g,h,F',cls=Function)
>>> eq=f(x,y,z)+g(y,x,w)*h(1,u,t)**cos(F(x,y,1,w))
>>> eq.replace(
... lambda x: isinstance(x, AppliedUndef),
... lambda x: x.func(*list(reversed(x.args))))
f(z, y, x) + g(w, x, y)*h(t, u, 1)**cos(F(w, 1, y, x))
If you wanted to apply such a transformation to all functions then use Function instead of AppliedUndef.

Can a complex number be unpacked into x and y in python?

I want to be able to unpack a complex number into its real and imaginary parts like so:
>>> z = 3 + 5j
>>> x, y = z
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot unpack non-iterable complex object
but I get an error. Of course I can do:
>>> x, y = z.real, z.imag
but this seems repetitive and not very readable, I can improve this by writing a function like this:
def unpack(z: complex) -> tuple[float, float]:
return z.real, z.imag
x, y = unpack(z)
but I feel like there should be a better way.
Do you know a better way to unpack a complex number into its real and imaginary parts?
One thing you can do is subclass complex and implement a custom __iter__ method that returns the unpacked version you seek.
I chose to call it complez in this case, since z is often a symbol for a complex number.
import cmath
class complez(complex):
def __iter__(self):
return iter((self.real, self.imag))
z = complez(3, 5)
x, y = z
print(x, y)
# 3.0 5.0
You can also do the following, if you don't mind overriding the existing complex. Using this approach allows you to continue using the complex class throughout your code without needing to make any changes to naming.
import builtins
import cmath
class complex(builtins.complex):
def __iter__(self):
return iter((self.real, self.imag))
z = complex(3, 5)
x, y = z
print(x, y)
# 3.0 5.0
(Technically, you could also drop the use of the builtins module).
In either case, the standard unpacking operator would also work, e.g.
print(*z)
# 3.0 5.0
NOTE: This solves your immediate problem, but it does have consequences. When you perform operations on these new numbers, e.g. adding 2 complez numbers, you would get a complex number result -- which won't have the convenient __iter__ defined, so you'd have to override other methods to account for this side-effect. For example:
def __add__(self, other):
return complez(super().__add__(other))
Hence, this approach does result in more overhead in producing a fully general solution. But, this is the tradeoff for having the convenience of natural unpacking.

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])

Simplify nested exponentials and logs with sympy

How can I get sympy to simplify an expression like log(exp(exp(x))) to exp(x)? It seems to work on simpler expressions like exp(log(x)) => x. This is a minimal example showing what I've tried so far:
import sympy
from sympy import exp, log
x = sympy.symbols('x')
a = exp(log(x))
print a
# Gives `x` automatically, no call to simplify needed
b = log(exp(exp(x)))
print sympy.simplify(b), sympy.powsimp(b,deep=True)
# Gives `log(exp(exp(x)))` back, expected `exp(x)`
This is happening because of lack of information. I think you want to do this:
In [7]: x = Symbol('x', real=True)
In [8]: (log(exp(exp(x)))).simplify()
Out[8]: exp(x)

Python - can't convert value from sympy.core.add.Add class to float

Here's my problem (illustraded with an applicable example):
... some code
### x=0.5*t*(copysign(1, t - 0.5) + 1) + 0.1
### x=string value
X= Matrix(len(x),1,x)
>>>print X[0]
0.5*t*(copysign(1, t - 0.5) + 1) + 0.1
>>>print type(X[0])
<class 'sympy.core.add.Add'>
t1=linspace(0,1,2)
REF=[]
for i in range(len(t1)):
REF.append(M_ref[0].subs(t,t1[i]))
>>>print REF
0.100000000000000, 0.5*copysign(1, 0.5) + 0.6
So REF[0] is from the 'sympy.core.numbers.Float' class, but REF[1] is from the 'sympy.core.add.Add' class (as are the rest of list values when I expand the linspace). Therefore I can't use them in the rest of my code. I tried to use evalf but that didn't solve the problem.
I need the values in the REF list to be all floats (or integers).
Any help would be appreciated.
I think I understand what is happening.
You are converting your input from strings. SymPy doesn't have a function called copysign, and sympify doesn't use the math library, so it just creates Function('copysign'), which is an unevaluated object. If you want to evaluate it as the math copysign from the get-go, you can add {'copysign': math.copysign} as a second argument when you call sympify, like sympify('copysign(1, 2)', {'copysign': math.copysign}). If you want a symbolic version, you will need to create one, as it doesn't exist in SymPy yet. Something like
class copysign(Function):
nargs = 2
#classmethod
def eval(cls, x, y):
if x.is_number and y.is_number:
return math.copysign(x, y)
That will work symbolically, but as soon as both arguments are numeric, it will evaluate using the math copysign.
>>> x, y = symbols('x y')
>>> copysign(x, y)
copysign(x, y)
>>> copysign(1, -2)
-1.0
Really, a better way to do it would be to reimplement the copysign logic symbolically, so that it always returns a SymPy type, but I'll leave that to you as an exercise. You can look at how sign is implemented in SymPy to get an idea.

Categories

Resources