Plotting Specific Regions - python

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.

Related

Thoughts and question when implementing animation plot of Collatz Conjecture (3X+1)

Thanks to Veritasium great video about the topic, I was planning to do a quick replication of the animation he showed in the video where the number bouncing up and down until hit the number 1, depending on the initial number.
Below I figured out a version of code to implement the animation. But I have a question and confusion while constructing the code.
I found that if I don't initialize the y-data as y=np.empty(100) instead with a empty list, it will throw an error list assignment index out of range
So I'm very confused why I can't start with a empty list, because I know, depending on the value of y_start the len(y) varies. If I can collect those calculated y value into a list (converting them into array later) then I don't have to go the whole night-yard setting plt.xlim(1,100) (instead, I could just set plt.xlim(0,len(y)) also due to the potential remaining 0.0 value in the final y-data, I have to add additional condition (2nd after and) in the if statement -> if y[i] % 2 == 0 and y[i] != 0: Otherwise, it goes haywire even after y reached the value 1....
In addition, I want to add y-data's value displaying on top of the each individual point, but I have no clue how to implement that in the code...It would be greatly appreciate if anyone can help on this issue to make the animation looks more "complete"!
Here is the code that I've tested
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import pyplot as plt
def odd(num):
return (num*3)+1
def even(num):
return num // 2
y_start = 33
x = np.linspace(0, 100, 100)
# y = []
def collatz():
y = np.empty(100)
y[0] = y_start
for i in range(0,100):
if y[i] % 2 == 0 and y[i] != 0:
y[i+1] = even(y[i])
else:
y[i+1] = odd(y[i])
if y[i+1] == 1:
break
return y
y = collatz()
fig = plt.figure()
plt.xlim(1,100)
plt.ylim(1,max(y))
draw, = plt.plot([],[])
def update(idx):
draw.set_data(x[:idx], y[:idx])
return draw,
a = FuncAnimation(fig, update, frames=len(x), interval=90)
plt.show()
So again, my questions are,
why starting with an empty list to contain calculated y fails?
Also, in the early version, inside the collatz function definition, my code was like:
def collatz():
y = np.empty(100)
y[0] = y_start
for i in range(0,100):
while y[i] != 1:
if y[i] % 2 == 0 and y[i] != 0:
y[i+1] = even(y[i])
else:
y[i+1] = odd(y[i])
if y[i+1] == 1:
break
return y
The compilation freezes, the code seems undergoes an infinite cycle...I don't know, is it the usage of the while statement ? what should I do/add to correct it?
I have rewritten your code (works on my machine), and will try to answer your questions
You cannot start from an empty list for y because the collatz() function needs a starting point. Hence, if y is empty, there is nothing to start from and the function fails. In the new code below I have added a parameter start to your function (in the code: 49). This is now the new starting point of your function. Note that if you want a random starting point instead of one defined by you, you can delete start, and replace y = [start] with y = [int(np.random.randint(1, 100, 1))] or another code that draws a random integer.
Now collatz uses a while loop: it works as long as y is larger than 1 (hence for y = 0 or 1 it will stop). Note that the -1 operator means 'the last element added to y'. For each number it does the even() or odd() function, and then it adds the number to the list using append. This ensures that the list is only as long as it needs to be. Note that in this case a while loop is the best option since you don't know how long the loop will last. When you have a fixed amount of iterations, a for loop should be chosen.
Finally, x is determined based on the length of y.
from matplotlib.animation import FuncAnimation
from matplotlib import pyplot as plt
def odd(num):
return (num*3)+1
def even(num):
return num // 2
def collatz(start):
y = [start]
while y[-1] > 1:
if y[-1] % 2 == 0:
y.append(even(y[-1]))
else:
y.append(odd(y[-1]))
return y
y = collatz(49)
x = list(range(len(y)))
fig = plt.figure()
plt.xlim(1,len(y))
plt.ylim(1,max(y))
draw, = plt.plot([],[])
def update(idx):
draw.set_data(x[:idx], y[:idx])
return draw
a = FuncAnimation(fig, update, frames=len(x), interval=90)
plt.show()

Plotting a graph given function definition

I'm currently trying to plot a graph of iterations of a certain function in python. I have defined the function as stated below but I am unsure on how to plot the graph such that the y value is on the y axis and the iteration number is on the x axis.
So, I have tried using the plt.plot function with different values in as my x values but using logistic(4, 0.7) as the y value for the y axis.
def logistic(A, x):
y = A * x * (1 - x)
return y
But each return an error. Can anyone shed any light on this, I want to do a total of 1000 iterations.
I dont understand much what you are saying concerning x being number ofiteration while you are showing us function logistic(4, 0.7). As far as I know, iterations is integer, whole number. You cant iterate just halfly or partially
def logistic(A, x):
y = A * x * (1 - x)
return y
A = 1
x_vals = []
y_vals = []
for x in range(1,1000):
x_vals.append(x)
y_vals.append(logistic(A,x))
#plt.plot(x_vals,y_vals) # See every iteration
#plt.show()
plt.plot(x_vals,y_vals) # See all iterations at once
plt.show()
Ah, the logistic map. Are you trying to make a cobweb plot? If so, your error may be elsewhere. As others have mentioned, you should post the error message and your code, so we can better help you. However, based on what you've given us, you can use numpy.arrays to achieve your desired result.
import numpy as np
import matplotlib.pyplot as plt
start = 0
end = 1
num = 1000
# Create array of 'num' evenly spaced values between 'start' and 'end'
x = np.linspace(start, end, num)
# Initialize y array
y = np.zeros(len(x))
# Logistic function
def logistic(A, x):
y = A * x * (1 - x)
return y
# Add values to y array
for i in range(len(x)):
y[i] = logistic(4, x[i])
plt.plot(x,y)
plt.show()
However, with numpy.arrays, you can omit the for loop and just do
x = np.linspace(start, end, num)
y = logistic(4, x)
and you'll get the same result, but faster.

Implementation of a threshold detection function in Python

I want to implement following trigger function in Python:
Input:
time vector t [n dimensional numpy vector]
data vector y [n dimensional numpy vector] (values correspond to t vector)
threshold tr [float]
Threshold type vector tr_type [m dimensional list of int values]
Output:
Threshold time vector tr_time [m dimensional list of float values]
Function:
I would like to return tr_time which consists of the exact (preferred also interpolated which is not yet in code below) time values at which y is crossing tr (crossing means going from less then to greater then or the other way around). The different values in tr_time correspond to the tr_type vector: the elements of tr_type indicate the number of the crossing and if this is an upgoing or a downgoing crossing. For example 1 means first time y goes from less then tr to greater than tr, -3 means the third time y goes from greater then tr to less then tr (third time means along the time vector t)
For the moment I have next code:
import numpy as np
import matplotlib.pyplot as plt
def trigger(t, y, tr, tr_type):
triggermarker = np.diff(1 * (y > tr))
positiveindices = [i for i, x in enumerate(triggermarker) if x == 1]
negativeindices = [i for i, x in enumerate(triggermarker) if x == -1]
triggertime = []
for i in tr_type:
if i >= 0:
triggertime.append(t[positiveindices[i - 1]])
elif i < 0:
triggertime.append(t[negativeindices[i - 1]])
return triggertime
t = np.linspace(0, 20, 1000)
y = np.sin(t)
tr = 0.5
tr_type = [1, 2, -2]
print(trigger(t, y, tr, tr_type))
plt.plot(t, y)
plt.grid()
Now I'm pretty new to Python so I was wondering if there is a more Pythonic and more efficient way to implement this. For example without for loops or without the need to write separate code for upgoing or downgoing crossings.
You can use two masks: the first separates the value below and above the threshold, the second uses np.diff on the first mask: if the i and i+1 value are both below or above the threshold, np.diff yields 0:
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0, 8 * np.pi, 400)
y = np.sin(t)
th = 0.5
mask = np.diff(1 * (y > th) != 0)
plt.plot(t, y, 'bx', markersize=3)
plt.plot(t[:-1][mask], y[:-1][mask], 'go', markersize=8)
Using the slice [:-1] will yield the index "immediately before" crossing the threshold (you can see that in the chart). if you want the index "immediately after" use [1:] instead of [:-1]

Complete turn x angle values are mapped to half turn using asin function, how to mirror them back?

I have angles that form a complete turn in an array x, from -90 to 270 e.g. (it may be defined otherwise, like from 0 to 360 or -180 to 180) with step 1 or whatever.
asin function is valid only between -90 and +90.
Thus, angles < -90 or > 90 would be "mapped" between these values.
E.g. y = some_asin_func(over_sin(x)) will end up in an y value that is always between -90 and +90. So y is stuck between -90 and +90.
I do need to retrieve to which x-input is y related, because it's ambiguous yet: for example, the function over (x) will give the same y values for x = 120 and x = 60, or x = -47 and x = 223. Which is not what I want.
Put an other way; I need y making a complete turn as x does, ranging from where x starts up to where x ends.
An image will be better:
Here, x ranges between -90 (left) to 270 (right of the graph).
The valid part of the curve is between x=-90 and x=+90 (left half of the graph).
All other values are like mirrored about y=90 or y=-90.
For x=180 for example, I got y=0 and it should be y=180.
For x=270, I have y=-90 but it should be y=270, thus +360.
Here's a code sample:
A = 50 # you can make this value vary to have different curves like in the images, when A=0 -> shape is triangle-like, when A=90-> shape is square-like.
x = np.linspace(-90,270,int(1e3))
u = np.sin(math.pi*A/180)*np.cos(math.pi*x/180)
v = 180*(np.arcsin(u))/math.pi
y = 180*np.arcsin(np.sin(math.pi*x/180)/np.cos(math.pi*v/180))/math.pi
plt.plot(x,y)
plt.grid(True)
Once again, first left half of the graph is completely correct.
The right half is also correct in its behavior, but in final, here, it must be mirrored about an horizontal axis at position y=+90 when x>90, like this:
That is, it's like the function is mirrored about y=-90 and y=+90 for y where x is out of the range [-90,+90] and only where where x is out of the range [-90,+90].
I want to un-mirror it outside the valid [-90,+90] range:
about y=-90 where y is lower than -90
about y=+90 where y is greater than +90
And of course, modulo each complete turn.
Here an other example where x ranges from -180 to 180 and the desired behavior:
Yet:
Wanted:
I have first tested some simple thing up now:
A = 50
x = np.linspace(-180,180,int(1e3))
u = np.sin(math.pi*A/180)*np.cos(math.pi*x/180)
v = 180*(np.arcsin(u))/math.pi
y = 180*np.arcsin(np.sin(math.pi*x/180)/np.cos(math.pi*v/180))/math.pi
for i,j in np.ndenumerate(x):
xval = (j-180)%180-180
if (xval < -90):
y[i] = y[i]-val
elif (xval > 90):
y[i] = y[i]+val
plt.plot(x,y);
plt.grid(True)
plt.show()
which doesn't work at all but I think the background idea is there...
I guess it may be some kind of modulo trick but can't figure it out.
Here a solution that fixes the periodicity of the cos function 'brute force' by calculating an offset and a sign correction based on the x value. I'm sure there is something better out there, but I would almost need a drawing with the angles and distances involved.
from matplotlib import pyplot as plt
import numpy as np
fig, ax = plt.subplots(1,1, figsize=(4,4))
x = np.linspace(-540,540,1000)
sign = np.sign(np.cos(np.pi*x/180))
offset = ((x-90)//180)*180
for A in range(1,91,9):
u = np.sin(np.pi*A/180)*np.cos(np.pi*x/180)
v = 180*(np.arcsin(u))/np.pi
y = 180*np.arcsin(np.sin(np.pi*x/180)/np.cos(np.pi*v/180))/np.pi
y = sign*y + offset
ax.plot(x,y)
ax.grid(True)
plt.show()
The result for the interval [-540, 540] looks like this:
Note that you can get pi also from numpy, so you don't need to import math -- I altered the code accordingly.
EDIT:
Apparently I first slightly misunderstood the OP's desired output. If the calculation of offset is just slightly changed, the result is as requested:
from matplotlib import pyplot as plt
import numpy as np
fig, ax = plt.subplots(1,1, figsize=(4,4))
x = np.linspace(-720,720,1000)
sign = np.sign(np.cos(np.pi*x/180))
offset = ((x-90)//180 +1 )*180 - ((x-180)//360+1)*360
for A in range(1,91,9):
u = np.sin(np.pi*A/180)*np.cos(np.pi*x/180)
v = 180*(np.arcsin(u))/np.pi
y = 180*np.arcsin(np.sin(np.pi*x/180)/np.cos(np.pi*v/180))/np.pi
y = sign*y + offset
ax.plot(x,y)
ax.grid(True)
plt.show()
The result now looks like this:
Thank you #Thomas Kühn, it seems fine except I wanted to restrict the function in a single same turn in respect to y-values. Anyway, it's only aesthetics.
Here's what I found by my side. It's maybe not perfect but it works:
A = 50
u = np.sin(math.pi*A/180)*np.cos(math.pi*x/180)
v = 180*(np.arcsin(u))/math.pi
y = 180*np.arcsin(np.sin(math.pi*x/180)/np.cos(math.pi*v/180))/math.pi
for i,j in np.ndenumerate(x):
val = (j-180)%360-180
if (val < -90):
y[i] = -180-y[i]
elif (val > 90):
y[i] = 180-y[i]
Here are some expected results:
Range from -180 to +180
Range from 0 to +360
Range from -720 to +720
Range from -360 to +360 with some different A values.
Funny thing is that it reminds me some electronics diagrams as well.
Periodic phenomenons are everywhere!

Plot a piecewise function in 3D

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.

Categories