matplotlib, how to plot 3d 2 variable function under given conditions - python

Plotting the function:
(x1 - 3)^2 + (x2 - 2)^2
With constraints:
x1^2 - x2 - 3 <= 0
x2 - 1 <= 0
-x1 <= 0
The equation can also be found here.
I am trying to solve this graphically using matplotlib
but ended up with the above graph using the code below (the question i found that helped me with the code) which is missing the first condition.
import matplotlib.pyplot as plt
from numpy import arange
from pylab import meshgrid
# function to be plotted
def z_func(a, b):
return (a - 3) * (a - 3) + (b - 2) * (b - 2)
x1 = arange(15.0, 0, -0.1) # x1 >= 0 according to given conditions
x2 = arange(-15.0, 1, 0.1) # x2 <= 1 according to given conditions
X1,X2 = meshgrid(x1, x2)
Z = z_func(X1, X2)
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X1, X2, Z, rstride=1, cstride=1, cmap=cm.RdBu,linewidth=0, antialiased=False)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
ax.set_xlabel('x-axis')
ax.set_ylabel('y-axis')
ax.set_zlabel('z-axis')
ax.view_init(elev=25, azim=-120)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
in which way shall the above code be altered in order to also take the first condition into account?
thanks

You can filter the array to plot and set all values outside the condition to nan:
Z[X1**2 - X2 - 3 > 0] = np.nan
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D
import numpy as np
from pylab import meshgrid
# function to be plotted
def z_func(a, b):
return (a - 3) * (a - 3) + (b - 2) * (b - 2)
x1 = np.arange(15.0, 0, -0.1) # x1 >= 0 according to given conditions
x2 = np.arange(-15.0, 1, 0.1) # x2 <= 1 according to given conditions
X1,X2 = meshgrid(x1, x2)
Z = z_func(X1, X2)
# set all values outside condition to nan
Z[X1**2 - X2 - 3 > 0] = np.nan
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X1, X2, Z, rstride=1, cstride=1,vmin=0, vmax=np.nanmax(Z),
cmap=plt.cm.RdBu,linewidth=0, antialiased=False)
ax.set_xlabel('x-axis')
ax.set_ylabel('y-axis')
ax.set_zlabel('z-axis')
ax.view_init(elev=25, azim=-120)
ax.set_ylim(0,4)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()

Related

The truth value of an array with more than one element is ambiguous. Use a.any() or a.all(). WHILE plotting 3d graph

import numpy as np
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
y = [4, 2]
def objectivedraw(a, b, y):
return -1 * (y[0] + 0.75 * max((1 - b) * y[0] - (y[1] + a), 0) - 0.5 * max((y[1] + a) - (1 - b) * y[0], 0) \
+ y[1] + 0.75 * max((1 - b) * y[1] - (y[0] + a), 0) - 0.5 * max((y[0] + b) - (1 - b) * y[1], 0))
a = np.arange(0, 3.0, 0.1)
b = np.arange(0, 1, 0.1)
A, B = np.meshgrid(a, b) # grid of point
Z = objectivedraw(A, B,y) # evaluation of the function on the grid
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
cmap=cm.RdBu, linewidth=0, antialiased=False)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
This is the code that I am using. The part related to plotting, i.e., after defining Z was coped from https://dzone.com/articles/how-plot-function-two (second block of code on the website). I am getting an error message:
File "C:/Users/rohan/PycharmProjects/untitled/plot utility.py", line 12, in objectivedraw
+ y[1] + 0.75 * max((1 - b) * y[1] - (y[0] + a), 0) - 0.5 * max((y[0] + b) - (1 - b) * y[1], 0))
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
I cannot figure out how to fix it. I think its because of my function.
EDIT: I am trying to make a Fehr-Schmidt utility function, so that's what the function is.
For Z = objectivedraw(A, B, y) to work with A and B being (2D) numpy arrays, and Z is to be expected also a numpy array, function objectivedraw should be compatible with numpy. In numpy, functions on an array are executed element by element, e.g. np.sin(A) would have same number of elements and the same number of dimensions as A but with all elements replaced by their sine.
The #np.vectorize decorator can make a function vectorized. To be compatible with numpy, the max operators should be replaced by np.maximum. Also, the vectorization doesn't know how to cope with the list y. In this case the elements of y can be passed one by one.
import numpy as np
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
#np.vectorize
def objectivedraw(a, b, y0, y1):
return -1 * (y0 + 0.75 * np.maximum((1 - b) * y0 - (y1 + a), 0) - 0.5 * np.maximum((y1 + a) - (1 - b) * y0, 0)
+ y1 + 0.75 * np.maximum((1 - b) * y1 - (y0 + a), 0) - 0.5 * np.maximum((y0 + b) - (1 - b) * y1, 0))
y = [4, 2]
a = np.arange(0, 3.0, 0.1)
b = np.arange(0, 1, 0.1)
A, B = np.meshgrid(a, b) # grid of point
Z = objectivedraw(A, B, y[0], y[1]) # evaluation of the function on the grid
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(A, B, Z, rstride=1, cstride=1,
cmap=cm.RdBu, linewidth=0, antialiased=False)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()

Finding the x coordinate for the max value of y

I have a function that is split in 3. (0 < x < L1) (L1 < x < a) (a < x L2).
I need to add a notation on the plot for the max value no matter where x is on the (0 < x < L2).
I have:
c1 = np.arange(0,L1+0.1, 0.1)
c2 = np.arange(L1,a+0.1, 0.1)
c3 = np.arange(a,L+0.1, 0.1)
y1 = -q*c1
y2 = -q*c2 + RAV
y3 = -q*c3 + RAV - P
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
ax1.fill_between(c1, y1)
ax1.fill_between(c2, y2)
ax1.fill_between(c3, y3)
ax1.set_title('S curve')
Mmax1=np.max(y1)
Mmax2=np.max(y2)
Mmax3=np.max(y3)
Mmax= round(max(Mmax1,Mmax2,Mmax3), 2)
Now I want to take find the x coordinate of the y value Mmax, but I don't know how to use something like x[np.argmax(Mmax)] where x = a.any(c1, c2, c3).
I need the x coordinate so that I can plot it in, where the value occurs
ax2.annotate(text2,
xy=(max_x, Mmax), xycoords='data',
xytext=(0, 30), textcoords='offset points',
arrowprops=dict(arrowstyle="->"))
How can I fix it? Thank you!
Here I would illustrate what may be useful for you,As I don't have you complete code to create your desire function I have defined 3 simple function with following description:
by out_arr=np.maximum.reduce([y1,y2,y3]) we would have maximum in ever x and by result = np.where(out_arr == np.amax(out_arr)) we find out which index have maximum value. then maximum pint would be point=[Max_X,out_arr[Max_X]]
import matplotlib.pyplot as plt
import numpy as np
x= np.arange(0., 6., 1)
y1=x
y2=x**2
y3=x**3
out_arr=np.maximum.reduce([y1,y2,y3])
result = np.where(out_arr == np.amax(out_arr))
Max_X=result[0]
print(Max_X)
point=[Max_X,out_arr[Max_X]]
plt.plot(Max_X,out_arr[Max_X],'ro')
# red dashes, blue squares and green triangles
plt.plot(x, y1, 'r--', x, y2, 'bs', x, y3, 'g^')
plt.show()

Creating non symmetrical axes in matplotlib

I'm trying to create the graph present in the image bellow. I'm new on matplotlib so I don't know how to create the axis without insert some data. For example, if a have x and y and plot(x,y), the graph will be create and the axis too, but the axis will be correlated with the data. In the graph attach, the curves are isolines, so the correlation of the data doesn't depend exactly of the axis (depend by a function that I have described in the code).
How can I proceed?
You can use plt.xlim and plt.ylim to set the limits on the axis. With plt.grid you can control how to show the gridlines. set_major_locator can set the position for the ticks and corresponding gridlines.
The position for the text could be calculated by first finding the y on the line that corresponds to x=40. If that y would be too low, we can calculate an x value on the line corresponding to y=-1500. For the rotation of the text, we can take the tangent of the line, which needs to be corrected by the aspect ratio of the plot. To have the rotation still valid when the size of the plot is changed, a fixed aspect ratio can be set.
Here is some demonstration code to get you started:
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
import numpy as np
fig, ax = plt.subplots()
aspect = 1 / 300
ax.set_aspect(aspect) # a fixed aspect ratio is needed so the text rotation stays equal to the line rotation
for iso in range(-5000, 16001, 1000):
x1 = 25 + iso / 100
x2 = -50
y1 = -1000
y2 = 7000 + iso
plt.plot([x1, x2], [y1, y2], c='crimson', lw=2 if iso % 5000 == 0 else 0.5)
if iso % 5000 == 0:
# xt,yt: x,y value for text; first try y position when x = 40; if too small find x position for y = -1500
xt = 40
yt = (y1 * (xt - x2) + y2 * (x1 - xt)) / (x1 - x2)
if yt < y1:
yt = -1500
xt = (x1 * (yt - y2) + x2 * (y1 - yt)) / (y1 - y2)
ax.text(xt, yt, iso if iso != 0 else "zero",
{'ha': 'center', 'va': 'center', 'bbox': {'fc': 'lightgoldenrodyellow', 'pad': 2, 'alpha': 0.7}},
rotation=np.arctan(aspect * (y1 - y2) / (x1 - x2)) / np.pi * 180)
ax.xaxis.set_major_locator(MultipleLocator(10))
ax.xaxis.set_minor_locator(MultipleLocator(5))
ax.xaxis.set_major_formatter(FormatStrFormatter('%d°C'))
ax.yaxis.set_major_locator(MultipleLocator(5000))
ax.yaxis.set_minor_locator(MultipleLocator(1000))
plt.grid(True, which='major', color='blue', linestyle='--', lw=0.5)
plt.grid(True, which='minor', color='blue', linestyle='--', lw=0.2)
plt.xlim(-50, 50)
plt.ylim(-5000, 20000)
plt.show()

ValueError: need more than 1 value to unpack with matplotlib

I have a 3D array and would like to make a 2D plot using plt.pcolormesh but I keep getting the error
ValueError: need more than 1 value to unpack.
My x and y arrays are both of length 59 and the z array is length 59*59=3481 since x and y is now a matrix.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
points = [(54.5, 17.041667, 31.993), (54.5, 17.083333, 31.911), (54.458333, 17.041667, 31.945), (54.458333, 17.083333, 31.866)]
points = sorted(points) # order points by x, then by y
(x1, y1, q11), (_x1, y2, q12), (x2, _y1, q21), (_x2, _y2, q22) = points
interp2d=[]
nums = np.linspace(x1, x2, num = 60, endpoint=True)
numms = np.linspace(y1, y2, num = 60, endpoint=True)
X, Y = np.meshgrid(nums[:-1], numms[:-1], indexing='xy')
for i in range(len(nums)-1):
for j in range(len(numms)-1):
x=nums[i]
y=numms[j]
interp = (q11 * (x2 - x) * (y2 - y) +
q21 * (x - x1) * (y2 - y) +
q12 * (x2 - x) * (y - y1) +
q22 * (x - x1) * (y - y1)
) / ((x2 - x1) * (y2 - y1) + 0.0)
interp2d.append(interp)
interp2d = np.asarray(interp2d)
fig, (ax1, ax2) = plt.subplots(1,2, sharex=True)
ax2.pcolormesh(X,Y,interp2d, cmap=plt.cm.BuPu_r)
cbar = plt.colorbar()
plt.show()
The values of interp2d are expected to be 2D array. So, you might wanna look into that. Rest of the code is irrelevant to the question you asked. So, good luck!

Generate random points above and below a line in Python

I would like to generate random points on an x,y scatter plot that are either above or below a given line. For example, if the line is y=x I would like to generate a list of points in the top left of the plot (above the line) and a list of points in the bottom right of the plot (below the line). Here's is an example where the points are above or below y=5:
import random
import matplotlib.pyplot as plt
num_points = 10
x1 = [random.randrange(start=1, stop=9) for i in range(num_points)]
x2 = [random.randrange(start=1, stop=9) for i in range(num_points)]
y1 = [random.randrange(start=1, stop=5) for i in range(num_points)]
y2 = [random.randrange(start=6, stop=9) for i in range(num_points)]
plt.scatter(x1, y1, c='blue')
plt.scatter(x2, y2, c='red')
plt.show()
However, I generated the x and y points independently, which limits me to equations where y = c (where c is a constant). How can I expand this to any y=mx+b?
You can change the stop and start limits for y1 and y2 to be the line you want. You will need to decide where the plane ends (set lower and upper).
Note this only works for integers. You can use truncated multivariate distributions if you want something more sophisticated.
m, b = 1, 0
lower, upper = -25, 25
x1 = [random.randrange(start=1, stop=9) for i in range(num_points)]
x2 = [random.randrange(start=1, stop=9) for i in range(num_points)]
y1 = [random.randrange(start=lower, stop=m*x+b) for x in x1]
y2 = [random.randrange(start=m*x+b, stop=upper) for x in x2]
plt.plot(np.arange(10), m*np.arange(10)+b)
plt.scatter(x1, y1, c='blue')
plt.scatter(x2, y2, c='red')
You may as well have my answer too.
This way puts Gaussian noise above the line, and below. I have deliberately set the mean of the noise to 20 so that it would stand out from the line, which is y = 10*x + 5. You would probably make the mean zero.
>>> import random
>>> def y(x, m, b):
... return m*x + b
...
>>> import numpy as np
>>> X = np.linspace(0, 10, 100)
>>> y_above = [y(x, 10, 5) + abs(random.gauss(20,5)) for x in X]
>>> y_below = [y(x, 10, 5) - abs(random.gauss(20,5)) for x in X]
>>> import matplotlib.pyplot as plt
>>> plt.scatter(X, y_below, c='g')
>>> plt.scatter(X, y_above, c='r')
>>> plt.show()
Here's the plot.
There are many approaches possible, but if your only requirement is that they are above and below the y = mx + b line, then you can simply plug the random x values into the equation and then add or subtract a random y value.
import random
import matplotlib.pyplot as plt
slope = 1
intercept = 0
def ymxb(slope, intercept, x):
return slope * x + intercept
num_points = 10
x1 = [random.randrange(start=1, stop=9) for i in range(num_points)]
x2 = [random.randrange(start=1, stop=9) for i in range(num_points)]
y1 = [ymxb(slope, intercept, x) - random.randrange(start=1, stop=9) for x in x1]
y2 = [ymxb(slope, intercept, x) + random.randrange(start=1, stop=9) for x in x2]
plt.scatter(x1, y1, c='blue')
plt.scatter(x2, y2, c='red')
plt.show()
That looks like this:
Side of (x, y) is defined by the sign of y - mx - b. You can read it here, for example.
import random
import matplotlib.pyplot as plt
num_points = 50
x = [random.randrange(start=1, stop=9) for i in range(num_points)]
y = [random.randrange(start=1, stop=9) for i in range(num_points)]
m = 5
b = -3
colors = ['blue' if y[i] - m * x[i] - b > 0 else 'red' for i in range(num_points) ]
plt.plot([0, 10], [b, 10 * m + b], c='green')
plt.xlim((0, 10))
plt.ylim((0, 10))
plt.scatter(x, y, c=colors)
plt.show()

Categories