I have the following issue: I want to lambdify a sympy expression containing parametric integrals like Integral(tanh(a*x),(x,0,1)). I tried to do a manual implementation like here.
What we want is essentially that the integral gets converted to something like:
lambda theta: quad(lambda x: g(x,theta), a,b)[0]
where
g = sp.lambdify((x,param), f, modules='numpy'))
Consider the following MWE:
import sympy as sp
import numpy as np
from scipy.integrate import quad
def integral_as_quad(function, limits):
x, a, b = limits
param = function.free_symbols - {x}
f = sp.lambdify( (x,*param), function, modules='numpy')
return lambda y: quad(lambda x: f(x,y), a,b)[0]
a, x = sp.symbols('a,x')
I = sp.Integral(sp.tanh(a*x),(x,0,1))
K = integral_as_quad(sp.tanh(a*x),(x,0,1))
L = sp.lambdify(a, I, modules=['numpy', {'Integral':integral_as_quad}] )
Then calling for example K(1) returns the correct value. However L(1) gives
AttributeError: 'Mul' object has no attribute 'tanh'
Does anyone have an idea how to fix this?
NOTE: Doing it manually is no option, since the expressions I deal with are way more complicated and may contain several different integrals. So I really need to get the lambdify working.
I think returning a lambda from integral_as_quad cannot work, because this lambda will never be called, as the Integral object in SymPy is not callable. Instead, the parameter tuple can be passed to quad via its args argument. Another change I made is in the outer lambdification, replacing
modules=['numpy', {'Integral':integral_as_quad}]
with
modules=[{'Integral': integral_as_quad}, 'sympy']
The idea is that at this stage we don't need NumPy functions yet, we just want to replace the Integral by our callable. The order of modules list matters: the dictionary comes first to prevent SymPy from keeping Integral as an Integral.
Now L(1) returns the correct amount.
import sympy as sp
import numpy as np
from scipy.integrate import quad
def integral_as_quad(function, limits):
x, a, b = limits
param = tuple(function.free_symbols - {x})
f = sp.lambdify((x, *param), function, modules=['numpy'])
return quad(f, a, b, args=param)[0]
a, x = sp.symbols('a,x')
I = sp.Integral(sp.tanh(a*x), (x,0,1))
L = sp.lambdify(a, I, modules=[{'Integral': integral_as_quad}, 'sympy'])
So one possible workaround I have found, but I am unhappy with because it is too slow for my application, is the following:
def to_lambda(expr, param):
# Preprocessing
expr = expr.evalf()
f = sp.lambdify([param], expr, modules='sympy')
fun = lambda x: np.array(np.array(f(x).evalf()), dtype='float64')
return fun
So first, expr gets cast into a lambda function using sympy-functions, e.g. we have
f = lambda a: Integral(tanh(a*x),(x,0,1))
and then we use sympy's internal integrator via evalf() (slow!).
Also, don't ask me why theres the double np.array, if one puts the dtype='float64' into the first one, then it returns TypeError: __array__() takes 1 positional argument but 2 were given
Related
I'm trying to concatenate 3 lists. When I try to use concatenate, like so, I get an error (TypeError: 'list' object cannot be interpreted as an integer):
import numpy as np
a = [1]
b = [2]
c = [3]
z = np.concatenate(a, b, c)
But if I put "extra" parentheses, it works like so:
z = np.concatenate((a, b, c))
Why?
I am not sure what library you are using (concatenate is not a built-in python 3.x function). However, I'll explain what I think is going on.
When you call concatenate(a, b, c), the function concatenate is sent three parameters: a, b, and c. concatenate then performs some logic that is (presumably) not the desired behavior.
When you call concatenate((a, b, c)), a tuple (effectively a list that cannot be changed) is created with a value of (a, b, c), which is evaluated to ([1], [2], [3]). Then this tuple is passed to the concatenate function. The following code is actually equivalent to your second code snippet:
a = [1]
b = [2]
c = [3]
y = (a, b, c) # This evaluates to ([1], [2], [3]).
z = concatenate(y)
I hope I've explained this clearly enough. Here's an article that explains tuples in more depth, if I haven't: https://www.w3schools.com/python/python_tuples.asp
EDIT: Thanks for including the library. Here's the code for what you're probably trying to do:
import numpy as np
a = [1]
b = [2]
c = [3]
z = np.array(a + b + c) # Lists can be concatenated using the `+` operator. Then, to make a numpy array, just call the constructor
I'm kinda new to python, and I'm trying to minimize the function "fun" that is a composite of "Velo" (also a composite of "area"), but it is giving me de error: TypeError: fun() missing 3 required positional arguments: 'q', 'ro', and 'mi'
from math import pi
import scipy.optimize
def area (r):
return pi*r**2
def velo(a,q):
a = area(r)
return q/a
def fun (r, q, ro, mi):
v = velo (q,a)
fun = (ro*v*r)/mi
return fun
x0 = [2,3,5,6]
res = scipy.optimize.minimize(fun, x0)
print(res)
Can someone help out here?
Many things to say here, I will try to answer properly.
Environment
Which IDE are you using? I advise you VSCode for example, as it underlines some mistakes it can detect, great for typos aswell.
Local variables
In python, variables are defined locally in functions. It means that the variable r in a = area(r) is not defined in your velo function.
The same mistake occurs in v = velo(q,a), as a is not defined in the fun function. I advise you to check these mistakes first.
Answer to your specific question
The error is raised because the array is considered one argument. I will transform your code (with is not working because of the previous points) but solves your error
def fun(array):
r, q, ro, mi = array
v = velo (q,a)
res = (ro*v*r)/mi
return res
x0 = np.array([2,3,4,5])
res = scipy.optimize.minimize(fun, x0)
Lets say, we have a sympy function cos(x). Every function can be
presented by a tree, e.g. like the image here https://reference.wolfram.com/language/tutorial/ExpressionsAsTrees.html
I want to insert a parameter into every node of this expression tree, that means
cos(x) -> a*cos(b*x)
For more complicated expression, it should look like
(exp(x)+cos(x)*x)/(x) -> h*(b*exp(a*x)+f*(c*cos(d*x)*e*x))/(j*x)
where a,b,c,d,e,f,g,h,j are parameters, that I want to fit.
A helpful source could be https://docs.sympy.org/latest/tutorial/manipulation.html in the chapter "walking the tree". I tried to replace parts of the
expr.args
tuple, but it is not possible.
This is the expression:
from simply import symbols, exp, cos
x, y = symbols('x y')
expr = (exp(x)+cos(x)*y)/(x)
This might get you started:
>>> s = numbered_symbols('C')
>>> cform = ((exp(x)+cos(x)*x)/(x)).replace(
... lambda x:not x.is_Number,
... lambda x:x*next(s))
>>> cform
C1*C8*C9*(C2*C4*C5*x*cos(C3*x) + C7*exp(C6*x))/(C0*x)
>>> from sympy.solvers.ode import constantsimp, constant_renumber
>>> constantsimp(cform, [i for i in cform.atoms(Symbol) if i.name.startswith('C')])
C0*(C2*x*cos(C3*x) + C7*exp(C6*x))/x
>>> constant_renumber(_)
C1*(C2*x*cos(C3*x) + C4*exp(C5*x))/x
>>> eq = _
>>> cons = ordered(i for i in eq.atoms(Symbol) if i.name.startswith('C'))
>>> eq.xreplace(dict(zip(cons, var('a:z'))))
a*(b*x*cos(c*x) + d*exp(e*x))/x
I'm writing a simple function in python 3.6 to find the distance between two points in n-dimensional space. I pass the coordinates as one-dimensional lists to my function. Here is what I have written:
def Distance(a: list[float], b: list[float]):
dimension = len(a)
sum = 0
for i in range(dimension):
sum += (b[i]-a[i])**2
return math.sqrt(sum)
This should work just fine if both a and b are passed explicitly. But I want to add additional functionality: if only a is passed, its distance from the origin will be calculated.
In C++, the language I am most familiar with, I would do this using function overloading. In that case, the two functions would simply have a different number of parameters. But this isn't an option for me in Python.
Any help with this would be greatly appreciated. Thank you in advance.
Edit:
The updated definition
def Distance(a: List[float], b=None):
if b:
arg = sum((x1 - x2)**2 for x1, x2 in zip(a,b))
else:
arg = sum(x**2 for x in a)
return math.sqrt(arg)
works for both cases.
An idiomatic definition could be
def distance(a, b=None):
from math import sqrt
d2=sum((x1-x2)**2 for x1,x2 in zip(a,b)) if b else sum(x**2 for x in a))
return sqrt(d2)
I confide that it's possible to use type hints as well, but I'm not an expert.
As a final remark, you used sum as a name, while it's recommended that you do not mask builtin names (I have used the sum builtin in my function definition)
The prototype must indeed be with None, so you have to change the function types because None is obviously not a list:
def Distance(a: list[float], b=None):
Then:
if not b:
b = [0.] * dimension
I have a function that takes another function x as its argument. Function x can have 2 different types of signatures and I would like to enforce this through type hinting:
TYPE_A = Callable[[int, int], int]
TYPE_B = Callable[[int], int]
def my_func(x: Union[TYPE_A, TYPE_B]) -> None:
...determine x is of which type and use x accordingly...
I am running into 2 problems:
I don't know how to check whether x is of TYPE_A or TYPE_B. I tried using isinstance(x, TYPE_A) and it generates a type error.
If I am using another way to determine the type of x, for instance, using signature to determine the signature of x inside my_func (if x has 1 parameter or 2 parameters), mypy still thinks there is a type error whenever I run x:
from inspect import signature
def my_func(x: Union[TYPE_A, TYPE_B]):
sig = signature(x)
if len(sig.parameters.values()) == 1:
x(1) // mypy thinks this is a type error: too few args
else:
x(1, 2) // mypy thinks this is type error: too many args
Is there a way for me to write a function that takes another function as its input, and use type checking to enforce that the input function has the correct signature?
Unfortunately, it does seem like there is currently no way for mypy to infer the correct type for x, so I think the best that you can do is to use typing.cast to force x to the desired type:
from typing import *
from inspect import signature
TYPE_A = Callable[[int, int], int]
TYPE_B = Callable[[int], int]
def my_func(x: Union[TYPE_A, TYPE_B]):
sig = signature(x)
if len(sig.parameters.values()) == 1:
x = cast(TYPE_B, x)
x(1)
else:
x = cast(TYPE_A, x)
x(1, 2)