Plot a piecewise function in 3D - python

I am new to python and trying to 3d plot a piecewise function. I am trying to 3d plot the 'mainformula' function below on the z-axis as it varies with x and y ranging from 0 to 10, and constant = 1. But I can't quite seem to figure out the plotting method here.
from sympy import *
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
def mainformula(x,y,constant):
return Piecewise((subformula1(x,y,constant), y >= 0 and y < 3),(subformula2(x,y,constant),y>=3 and y <= 10))
def subformula1(x,y,constant):
return x + y + constant
def subformula2(x,y,constant):
return x - y - constant
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(0, 10, 0.25)
Y = np.arange(0, 10, 0.25)
constant = 1
X, Y = np.meshgrid(X, Y)
Z = mainformula(X,Y,constant)
surf = ax.plot_surface(X, Y, Z)
plt.show()
The error I get when I run that code is: "ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()"

You are working on arrays so it's never going to work to use array > 3 in a boolean context (for example with and) this will always give you the error you received. But you can always define your conditions as boolean masks and operate your formula on the appropriate elements:
def mainformula(x,y,constant):
z = np.zeros_like(x)
# Condition 1 indexes all elements where subformula 1 is valid
condition1 = np.logical_and(y >= 0, y < 3)
# condition1 = (y >= 0) & (y < 3) # is another way of writing it
z[condition1] = x[condition1] + y[condition1] + constant
# now do it in the range where subformula 2 is valid
condition2 = np.logical_and(y >= 3, y <= 10)
# condition1 = (y >= 3) & (y <= 10) # is another way of writing it
z[condition2] = x[condition2] - y[condition2] - constant
return z
This doesn't use sympy.Piecewise but works alright when only trying to plot. If you want seperate functions instead of doing it all in the main formula you need to change it a bit:
z[condition1] = subformula1(x[condition1], y[condition1], constant)
and similar for condition2.

The problem is you are trying to make logic assertions into arrays that do not have a direct one. It's difficult for Python to assert what this is:
y >= 0 and y < 3
So you'll have to change somewhat your code into something it can understand:
def mainformula(x,y,constant):
y2 = y[y>=0]
y2 = y2[y2<3]
y3 = y[y>=3]
y3 = y2[y2<=10]
return Piecewise((subformula1(x,y,constant), y2),(subformula2(x,y,constant),y3))
The problem is Piecewise function also does not seem to accept some arrays into one of the arguments you have. You'll have to rethink your problem, perhaps by building a loop for the Piecewise function in order to be launched at every element.

Related

Plotting piecewise functions using Matplotlib

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
The above error is what I got for the below code intended to plot the piecewise function created. Can't figure it out.
import numpy as np
import matplotlib.pyplot as plt
def f(x):
if x>=0 and x<=1:
return x
elif x>1 and x<=2:
return 2-x
else:
return 0
xlist = np.linspace(0,1,num = 1000)
ylist = f(xlist)
plt.plot(ylist,xlist)
plt.show()
As per comments, you need to vectorize your method f (and also fix some mistakes):
import numpy as np
import matplotlib.pyplot as plt
def f(x):
y = np.empty_like(x)
mask1 = (0 <= x) & (x <= 1)
mask2 = (1 < x) & (x <= 2)
mask3 = np.logical_not((0 <= x) & (x <= 2)) #or ~((0 <= x) & (x <= 2))
y[mask1] = x[mask1]
y[mask2] = 2 - x[mask2]
y[mask3] = 0
return y
xlist = np.linspace(-1, 3, num = 1000)
ylist = f(xlist)
plt.plot(xlist, ylist)
plt.show()
You might also find that mask3 is not necessarry and vectorize a method f like this:
def f(x):
y = np.full_like(x, fill_value=0) #or np.zeros_like(x)
mask1 = (0 <= x) & (x <= 1)
mask2 = (1 < x) & (x <= 2)
y[mask1] = x[mask1]
y[mask2] = 2 - x[mask2]
return y
What I understood is , you are basically comparing an array with a number which should not be the case here, the code provided might help you with solution,
import numpy as np
import matplotlib.pyplot as plt
def f(x):
if x>=0 and x<=1:
return x
elif x>1 and x<=2:
return 2-x
else:
return 0
xlist = np.linspace(0,1,num = 1000,endpoint=False)
ylist = np.array([f(num) for num in xlist])
plt.plot(xlist,ylist)
plt.show()
You can use the function you have already defined and vectorize is using np.vectorize.
import numpy as np
import matplotlib.pyplot as plt
def f(x):
if x>=0 and x<=1:
return x
elif x>1 and x<=2:
return 2-x
else:
return 0. # make this change to ensure float values
xlist = np.linspace(0,1,num = 1000)
ylist = np.vectorize(f)(xlist)
plt.plot(ylist,xlist)
plt.show()
The issue you are running into in your code is that you are passing an array to f rather than applying f element-wise, so any comparison gives an array of True or False values; this is what is ambiguous in the error message. Using np.vectorize will change the function defined to apply to each element of the array and give you the desired output.

How to plot curve with given polynomial coefficients?

using Python I have an array with coefficients from a polynomial, let's say
polynomial = [1,2,3,4]
which means the equation:
y = 4x³ + 3x² + 2x + 1
(so the array is in reversed order)
Now how do I plot this into a visual curve in the Jupyter Notebook?
There was a similar question:
Plotting polynomial with given coefficients
but I didn't understand the answer (like what is a and b?).
And what do I need to import to make this happen?
First, you have to decide the limits for x in your plot. Let's say x goes from -2 to 2. Let's also ask for a hundred points on our curve (this can be any sufficiently large number for your interval so that you get a smooth-looking curve)
Let's create that array:
lower_limit = -2
upper_limit = 2
num_pts = 100
x = np.linspace(lower_limit, upper_limit, num_pts)
Now, let's evaluate y at each of these points. Numpy has a handy polyval() that'll do this for us. Remember that it wants the coefficients ordered by highest exponent to lowest, so you'll have to reverse the polynomial list
poly_coefs = polynomial[::-1] # [4, 3, 2, 1]
y = np.polyval(poly_coefs, x)
Finally, let's plot everything:
plt.plot(x, y, '-r')
You'll need the following imports:
import numpy as np
from matplotlib import pyplot as plt
If you don't want to import numpy, you can also write vanilla python methods to do the same thing:
def linspace(start, end, num_pts):
step = (end - start) / (num_pts - 1)
return [start + step * i for i in range(num_pts)]
def polyval(coefs, xvals):
yvals = []
for x in xvals:
y = 0
for power, c in enumerate(reversed(coefs)):
y += c * (x ** power)
yvals.append(y)
return yvals

Matplotlib - Plot over a triangular region

I could not remotely figure out how to define a plot over a triangular region in matplotlib. That is the first problem I have.
As a workaround, I thought to define the function using a conditional expression, to avoid problems where the function is not defined.
def f(x,y):
for a in x:
for b in y:
if a>b:
g = log(a-b)
else:
g = 0
return
x = np.linspace(0.1, 1000, 30)
y = np.linspace(0.1, 3, 30)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
but I get the error message
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
which at least made me understand (sort of) what meshgrid does.
To summarise:
1) what is the neatest way to plot a function over a triangle?
2) what is the advantages of plotting over a meshgrid, instead of defining a single array as ([X1,Y1], [X1,Y2], ..., [X1,YN], [X2, Y1], [X2, Y2], ..)?
Thanks a lot
The problem that you encounter is using a normal Python function for NumPy arrays. This does not work always as expected, especially when you use conditionals like <. You can simplify f as:
import math
def f(x,y):
if x > y:
return math.log(x-y)
else:
return 0.0 # or math.nan
and than make a numpy ufunc from it using np.frompyfunc:
f_np = np.frompyfunc(f,2,1)
Now you can do:
Z = f_np(X,Y)
Notes: If you intend to use plt.contourf(X,Y,Z), everything will clearer if you use y = np.linspace(0.1, 1000, 30) instead of y = np.linspace(0.1, 3, 30) so that the triangle is exactly half of the plot. If you use math.nan in f instead of 0.0, the triangle will be left blank in the contourplot.

Plotting Specific Regions

I am new to python. The problem is that, assume that we have two parameters, x and y, and four functions f_1, f_2, f_3 and f_4. Suppose that we know that:
If (x < 5 < y < 5+x) or (5 <= y < x) or (x= 5 and 5 < y < 10) then function f_1 is the maximum function.
If (5 < x < y < 5 + x) or (x <= y < 5) then function f_2 is the maximum function.
If (y < x < 5) or (y < 5 < x) or ( x = 5 and y < x) then function f_3 is the maximum function.
If y > x+5 then function f_4 is the maximum function.
I need to draw a plot with x-axis = x and y-axis = y which shows the regions under which each function is the maximum function.
I used the following code, however the resulted plot, shown below, is not accurate.
import numpy as np
from matplotlib import pyplot as plt
x = np.arange(0,10,.1)
y = np.arange(0,15,.2)
x,y = np.meshgrid(x,y)
maxf = np.zeros(shape = x)
maxf.fill(-9999.99)
for i in range(len(x)):
for j in range(len(y)):
if j<i<5 or j<5<i:
maxf[i,j] =1
elif i<5<=j<i+5 or 5<=j<i:
maxf[i,j] =2
elif 5<i<=j<i+5 or i<=j<5:
maxf[i,j] =3
elif i == 5 and j<5:
maxf[i,j]=1
elif i == 5 and 5<=j<10:
maxf[i,j]=2
elif j >= 5+i:
maxf[i,j]=4
plt.contourf(x,y,maxf)
plt.colorbar()
plt.show()
The result should have been sth like the following picture:
When you set the initial array to -9999.99 you now have to make sure you only contour the values that you want which is between 1-3. Since that value is so much bigger in magnitude it does not get included in your plot. Set your contour levels for your plot using this:
plt.contourf(x,y,maxf,[0,1,2,3])
Yields:
Update
I didn't notice before but you are using i,j like they are the numbers but they actually represent the indexes of the arrays which is throwing off your calculation. You need to know the index and the value so you can use enumerate. If this is still not correct, then you need to revisit your logic in your conditions.
import numpy as np
from matplotlib import pyplot as plt
y = np.arange(0,15,.01)
x = np.arange(0,10,.01)
Y,X = np.meshgrid(y,x)
maxf = np.zeros(shape = Y.shape)
maxf.fill(-9999.99)
for i,x_ in enumerate(x):
for j, y_ in enumerate(y):
if y_<x_<5 or y_<5<x_:
maxf[i,j] =3
elif x_<5<=y_<(x_+5) or 5<=y_<x_:
maxf[i,j] =1
elif 5<x_<=y_<(x_+5) or x_<=y_<5:
maxf[i,j] =2
elif x_ == 5 and y_<5:
maxf[i,j]=3
elif x_ == 5 and y_>=5:
maxf[i,j]=1
elif y_ >= (5+x_):
maxf[i,j]=4
plt.contourf(X,Y,maxf,[0,1,2,3,4])
plt.colorbar()
plt.show()
Final Note
Just because you add a condition does not mean it will get evaluated if another condition is met first. In this case your 4th function is never true because one of the other conditions is always met. If you want that condition first, then make it your first if statement. How you arrange your logical statements matters especially since you have lots of conditions and some of which overlap each other.

Solve nonlinear equation in python

I am trying to find the fundamental TE mode of the dielectric waveguide. The way I try to solve it is to compute two function and try to find their intersection on graph. However, I am having trouble get the intersect point on the plot.
My code:
def LHS(w):
theta = 2*np.pi*1.455*10*10**(-6)*np.cos(np.deg2rad(w))/(900*10**(-9))
if(theta>(np.pi/2) or theta < 0):
y1 = 0
else:
y1 = np.tan(theta)
return y1
def RHS(w):
y = ((np.sin(np.deg2rad(w)))**2-(1.440/1.455)**2)**0.5/np.cos(np.deg2rad(w))
return y
x = np.linspace(80, 90, 500)
LHS_vals = [LHS(v) for v in x]
RHS_vals = [RHS(v) for v in x]
# plot
fig, ax = plt.subplots(1, 1, figsize=(6,3))
ax.plot(x,LHS_vals)
ax.plot(x,RHS_vals)
ax.legend(['LHS','RHS'],loc='center left', bbox_to_anchor=(1, 0.5))
plt.ylim(0,20)
plt.xlabel('degree')
plt.ylabel('magnitude')
plt.show()
I got plot like this:
The intersect point is around 89 degree, however, I am having trouble to compute the exact value of x. I have tried fsolve, solve to find the solution but still in vain. It seems not able to print out solution if it is not the only solution. Is it possible to only find solution that x is in certain range? Could someone give me any suggestion here? Thanks!
edit:
the equation is like this (m=0):
and I am trying to solve theta here by finding the intersection point
edit:
One of the way I tried is as this:
from scipy.optimize import fsolve
def f(wy):
w, y = wy
z = np.array([y - LHS(w), y - RHS(w)])
return z
fsolve(f,[85, 90])
However it gives me the wrong answer.
I also tried something like this:
import matplotlib.pyplot as plt
x = np.arange(85, 90, 0.1)
f = LHS(x)
g = RHS(x)
plt.plot(x, f, '-')
plt.plot(x, g, '-')
idx = np.argwhere(np.diff(np.sign(f - g)) != 0).reshape(-1) + 0
plt.plot(x[idx], f[idx], 'ro')
print(x[idx])
plt.show()
But it shows:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
First, you need to make sure that the function can actually handle numpy arrays. Several options for defining piecewise functions are shown in
Plot Discrete Distribution using np.linspace(). E.g.
def LHS(w):
theta = 2*np.pi*1.455*10e-6*np.cos(np.deg2rad(w))/(900e-9)
y1 = np.select([theta < 0, theta <= np.pi/2, theta>np.pi/2], [np.nan, np.tan(theta), np.nan])
return y1
This already allows to use the second approach, plotting a point at the index which is closest to the minimum of the difference between the two curves.
import numpy as np
import matplotlib.pyplot as plt
def LHS(w):
theta = 2*np.pi*1.455*10e-6*np.cos(np.deg2rad(w))/(900e-9)
y1 = np.select([theta < 0, theta <= np.pi/2, theta>np.pi/2], [np.nan, np.tan(theta), np.nan])
return y1
def RHS(w):
y = ((np.sin(np.deg2rad(w)))**2-(1.440/1.455)**2)**0.5/np.cos(np.deg2rad(w))
return y
x = np.linspace(82.1, 89.8, 5000)
f = LHS(x)
g = RHS(x)
plt.plot(x, f, '-')
plt.plot(x, g, '-')
idx = np.argwhere(np.diff(np.sign(f - g)) != 0).reshape(-1) + 0
plt.plot(x[idx], f[idx], 'ro')
plt.ylim(0,40)
plt.show()
One may then also use scipy.optimize.fsolve to get the actual solution.
idx = np.argwhere(np.diff(np.sign(f - g)) != 0)[-1]
from scipy.optimize import fsolve
h = lambda x: LHS(x)-RHS(x)
sol = fsolve(h,x[idx])
plt.plot(sol, LHS(sol), 'ro')
plt.ylim(0,40)
plt.show()
Something quick and (very) dirty that seems to work (at least it gave theta value of ~89 for your parameters) - add the following to your code before the figure, after RHS_vals = [RHS(v) for v in x] line:
# build a list of differences between the values of RHS and LHS
diff = [abs(r_val- l_val) for r_val, l_val in zip(RHS_vals, LHS_vals)]
# find the minimum of absolute values of the differences
# find the index of this minimum difference, then find at which angle it occured
min_diff = min(diff)
print "Minimum difference {}".format(min_diff)
print "Theta = {}".format(x[diff.index(min_diff)])
I looked in range of 85-90:
x = np.linspace(85, 90, 500)

Categories