I want to plot a piecewise function, such as:
import sympy as sym
x = sym.symbols("x")
f = sym.Piecewise((-1, x < -1),
(x, sym.And(-1 <= x, x < 0)),
(x**2, sym.And(0 <= x, x < 1)),
(x**3, x >= 1))
sym.plotting.plot(f, (x, -3, 3))
However, when running this code, an exception was raised ...
AttributeError: 'BooleanFalse' object has no attribute 'evalf'
I think the problem may come from the two cases
sym.And(-1 <= x, x < 0)
and
sym.And(0 <= x, x < 1)
Here a python type 'bool' supposed, while the function 'evalf' can't convert the 'sympy' type 'BooleanFalse' into the python type 'bool'.
I wander how to deal with this problem, and is it possible to plot the piecewise functions without using the 'matplotlib' module?
Your function definition is overdone, sympy evaluates the conditions in order and returns the 1st expression for which the condition is True.
I don't understand exactly which went wrong in your definition but a simpler definition works for me
In [19]: f = sym.Piecewise(
(-1, x < -1),
(x, x < 0),
(x**2, x < 1),
(x**3, True))
....:
In [20]: sym.plotting.plot(f, (x, -3, 3))
Out[20]: <sympy.plotting.plot.Plot at 0x7f90cb9ec6d8>
In [21]:
PS I have understood why your plot fails, it's because plot tries to evaluate the condition feeding a value for x, but the condition is the constant BooleanFalse that's the result of evaluating sym.And() at the time of definition of your piecewise function.
Which version of sympy are you using?
I think this is a bug that's been fixed, but in the meantime, try this if you can't/don't want to update:
import sympy as sym
from sympy.abc import x
f = x**2
g = x**3
p = sym.Piecewise((-1, x < -1),
(x, x < 0),
(f, x < 1),
(g, True))
sym.plotting.plot(p, (x, -3, 3), adaptive=False)
EDIT:
you can write it as before with this method but as stated in gboffi's answer, I don't think sympy likes it... try this
import sympy as sym
from sympy.abc import x
f = x**2
g = x**3
p = sym.Piecewise((-1, x < -1),
(x, sym.And(-1 <= x, x < 0)),
(f, sym.And(0 <= x, x < 1)),
(g, x >= 1))
sym.plotting.plot(p, (x, -3, 3), adaptive=False)
Comment: I have a similar problem with sympy 1.0. The code below gives the AttributeError in 1.0, but not in version 0.7.6.1 which works fine.
f = Piecewise((0, x < 0), (0, x > L), (1+0.3*x, True))
plot(f.subs({L:1}))
Related
For Uni I'm doing this assignment where I have to approximate the difference between the sine function and its n-th Taylor approximation. When running the code plotting these two functions I run into the following error:
TypeError: ufunc 'add' output (typecode 'O') could not be coerced to provided output parameter (typecode 'd') according to the casting rule ''same_kind''
The weird thing (in my opinion) is that the program works fine for n <= 20, but when I choose anything above that, it throws this error.
Does anyone know where in my code the problem may lie? Thanks in advance.
import matplotlib.pyplot as plt
import numpy as np
import math
def constant(n, x):
return np.full(x.shape, (2*math.pi)**(n+1)/(math.factorial(n+1)))
def taylor_n(n,x):
val = 0
for i in range(1, n+1):
if i%2 == 1:
val += (-1)**((i-1)/2)* x**i/math.factorial(i)
return val
N = [1, 5, 10, 20, 50]
x = np.linspace(0,2*math.pi,100)
for n in N:
plt.plot(x, abs(np.sin(x) - taylor_n(n, x)))
plt.plot(x, constant(n, x))
Looks like float underflow. If cast to decimal it works:
import decimal
...
def taylor_n(n,x):
val = 0
for i in range(1, n+1):
if i%2 == 1:
val += np.array((-1) ** ((i - 1) / 2) * x ** i / math.factorial(i), dtype=np.dtype(decimal.Decimal))
return val
Inside taylor_n function expression ((-1) ** ((i - 1) / 2) * x ** i / math.factorial(i)) has type float64, but when i becomes greater the type of expression becomes complex128 and these types can't be summed.
Problem was only while N=50 (actually, N>20), another values calculated correctly. For N=50 plot is:
Is it possible to find the transformation expression to X from U(0, 1) in SymPy?
import sympy.stats as stat
import sympy as sp
x = sp.Symbol('x')
p = sp.Piecewise( (x + 1, (-1. <= x) & (x <= 0)), (1 - x, (x >= 0) & (x <=1 )), (0, True) )
X = stat.ContinuousRV(x, p, Interval(-1, 1))
cdf = stat.cdf(X)(x)
# Where to go from here?
stat.sample(X)
# TypeError: object of type 'ConditionSet' has no len()
sample in sympy/stats/crv.py
def sample(self):
172 """ A random realization from the distribution """
--> 173 icdf = self._inverse_cdf_expression()
174 return icdf(random.uniform(0, 1))
How can I find the inverse cdf expression from the custom piecewise?
By hand I get: 1 - sqrt(2-2u)
Is it possible with another library?
One issue is that cdf is a nested Piecewise object. These should be folded with piecewise_fold. (Aside: your formula for p has a floating point number 1., I replaced it by 1 to make SymPy's life easier.)
cdf = sp.piecewise_fold(cdf)
u = sp.Symbol('u', positive=True)
inv = sp.solveset(cdf - u, x, domain=sp.Interval(0, 1))
Now inv is
Intersection(Interval.Ropen(0, 1), {-sqrt(2)*sqrt(-u + 1) + 1, sqrt(2)*sqrt(-u + 1) + 1})
It's unfortunate that SymPy did not discard the second solution, which is obviously outside of the interval (0, 1). But at least the first one is correct.
You still can't use this for stat.sample, so any sampling would have to be coded directly. Two remarks aside:
SymPy is not a particularly effective tool for sampling, as it is a numerical task. In NumPy, sampling this specific (triangular) distribution is a one-liner:
>>> np.random.triangular(-1, 0, 1, size=(5,))
array([-0.40718329, 0.26692739, 0.84414925, 0.33518136, -0.7323011 ])
SymPy also has Triangular built in, not that it helps with sampling.
I am starting to use sympy. I computed a convolution, but the result was not correct:
This result is wrong: The correct result is
So what did I do wrong? I had used sympy to integrate piecewise fucntions before, with no problems...
The code:
from sympy import *
init_session()
f = lambda x: Piecewise( (1 , (x >= -1) & (x <= 1)) , (0 , True) )
Conv = lambda x: integrate( f(x-y)*f(y) , (y, -oo, +oo) )
There is nothing that you did wrong. It's Sympy having an issue with the product of two piecewise expressions. By calling piecewise_fold(f(x-y)*f(y)) you can see that it does not manage to sort this product out, leaving it as a nested Piecewise construction.
Piecewise((Piecewise((1, And(x - y <= 1, x - y >= -1)), (0, True)), And(y <= 1, y >= -1)), (0, True))
The symbolic integration routine trips up on this nested thing, which may be worth filing an issue on GitHub.
Workaround
If you flatten this nested Piecewise by hand, the integration works correctly.
g = Piecewise((1, And(x-y <= 1, x-y >= -1, y <= 1, y >= -1)), (0, True))
integrate(g, (y, -oo, +oo))
outputs Min(1, x + 1) - Min(1, x + 1, Max(-1, x - 1)) which is correct although perhaps not in the form one would expect.
I have following test program. My query is two folded: (1) Some how the solution is giving zero and (2) Is it appropriate to use this x2= np.where(x > y, 1, x) kind of conditions on variables ? Are there any constrained optimization routines in Scipy ?
a = 13.235
b = 70.678
def system(X, a,b):
x=X[0]
y=X[1]
x2= np.where(x > y, 1, x)
f=np.zeros(3)
f[0] = 2*x2 - y - a
f[1] = 3*x2 + 2*y- b
return (X)
func= lambda X: system(X, a, b)
guess=[5,5]
sol = optimize.root(func,guess)
print(sol)
edit: (2a) Here with x2= np.where(x > y, 1, x) condition, two equations becomes one equation.
(2b) In another variation requirement is: x2= np.where(x > y, x^2, x^3). Let me comments on these two as well. Thanks !
First up, your system function is an identity, since you return X instead of return f. The return should be the same shape as the X so you had better have
f = np.array([2*x2 - y - a, 3*x2 + 2*y- b])
Next the function, as written has a discontinuity where x=y, and this is causing there to be be a problem for the initial guess of (5,5). Setting the initial guess to (5,6) allows for the the solution [13.87828571, 14.52157143] to be found rapidly.
With the second example, again using an initial guess of [5,5] causes problems of discontinuity, using [5,6] gives a solution of [ 2.40313743, 14.52157143].
Here is my code:
import numpy as np
from scipy import optimize
def system(X, a=13.235, b=70.678):
x = np.where(X[0] > X[1], X[0]**2, X[0]**3)
y=X[1]
return np.array( [2*x - y - a, 3*x + 2*y - b])
guess = [5,6]
sol = optimize.root(system, guess)
print(sol)
I'm trying to integrate a piecewise function using Sagemath, and finding it to be impossible. My original code is below, but it's wrong due to accidental evaluation described here.
def f(x):
if(x < 0):
return 3 * x + 3
else:
return -3 * x + 3
g(x) = integrate(f(t), t, 0, x)
The fix for plotting mentioned on the website is to use f instead of f(t), but this is apparently unsupported for the integrate() function since a TypeError is raised.
Is there a fix for this that I'm unaware of?
Instead of defining a piecewise function via def, use the built-in piecewise class:
f = Piecewise([[(-infinity, 0), 3*x+3],[(0, infinity), -3*x+3]])
f.integral()
Output:
Piecewise defined function with 2 parts, [[(-Infinity, 0), x |--> 3/2*x^2 + 3*x], [(0, +Infinity), x |--> -3/2*x^2 + 3*x]]
The piecewise functions have their own methods, such as .plot(). Plotting does not support infinite intervals, though. A plot can be obtained with finite intervals
f = Piecewise([[(-5, 0), 3*x+3],[(0, 5), -3*x+3]])
g = f.integral()
g.plot()
But you also want to subtract g(0) from g. This is not as straightforward as g-g(0), but not too bad, either: get the list of pieces with g.list(), subtract g(0) from each function, then recombine.
g0 = Piecewise([(piece[0], piece[1] - g(0)) for piece in g.list()])
g0.plot()
And there you have it:
By extending this approach, we don't even need to put finite intervals in f from the beginning. The following plots g - g(0) on a given interval [a,b], by modifying the domain:
a = -2
b = 3
g0 = Piecewise([((max(piece[0][0], a), min(piece[0][1], b)), piece[1] - g(0)) for piece in g.list()])
g.plot()
In addition to using the Piecewise class, this can easily be fixed by defining g(x) as a Python function as well:
def f(x):
if(x < 0):
return 3 * x + 3
else:
return -3 * x + 3
def g(x):
(y, e) = integral_numerical(f, 0, x)
return y
Then plot(g) works just fine.