Plotting a piecewise function in python with numpy - python

Note: This is for homework so please don't post full code responses, just help on what I'm misusing would be appreciated
I'm trying to plot a piecewise defined function where when 0 < x <= 10 it will be a constant (KQ/10) and for x > 10 it will be KQ/x for 10 < x < 50. Currently my result comes back as a single value instead of my expected result of an array with a constant value up until x > 10 and then varying values until x = 50
My current code
import matplotlib.pyplot as plt
import scipy
x = np.linspace(0, 50, 1)
R = 10
r = np.linspace(10, 50, 1)
k = 1/(4*np.pi*constants.epsilon_0)
Q = 1
def inside(x):
return k*Q/R
def outer(x):
return k*Q/x
result = np.piecewise(
x,
[x <= R, x > R ],
[lambda x: inside(x), lambda x: outer(x)]
)
result
#plt.plot(x,result)
#plt.axis([0, 50,0, 500000])
#plt.xlabel('x')
#plt.ylabel('y')```

x = np.linspace(0, 50, 1)
isnt how linspace works... this only creates one data point ...
x = np.linspace(0, 50, 10000) ... would create 10k datapoints
perhaps you wanted np.arange(0,50,1) ?

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.

Test whole range of possible inputs for a given question

Is there a quick way to find the maximum value (float) from a function and the corresponding arguments x, y that are both integers between 0 and 100 (inclusive)? Do I need to use the assert function or something like that to get the range of all possible inputs?
def fun_A(x,y):
import math
if x == y:
return 0
first = math.cos((y%75)*(math.pi/180))
second = math.sin((x%30)*(math.pi/180))
return (first + second) / (abs(x - y))
For small problems like this it is probably fast enough to evaluate every possible combination and choose the maximum. The numpy library makes this easy to write and pretty fast as well:
import numpy as np
def fun_A(x, y):
first = np.cos((y%75)*(np.pi/180))
second = np.sin((x%30)*(np.pi/180))
return np.where(x == y, 0, (first + second) / (abs(x - y)))
x, y = np.mgrid[0:101, 0:101]
f = fun_A(x, y)
maxindex = np.argmax(f)
print('Max =', f.flat[maxindex], ' at x =', x.flat[maxindex], 'y =', y.flat[maxindex])
Output:
Max = 1.4591796850315724 at x = 89 y = 88
Things to note:
I've just replaced calls to math with calls to np.
x and y are matrices which allow us to evaluate every possible combination the two values in one function call.
I would do this for the tan function :
from math import tan
y = 0
x = 0
for x_iteration in range(0, 101):
if tan(x_iteration) > y :
x = x_iteration
y = tan(x_iteration)
x = int(x)
y = int(y)
It's fairly straightforward to write a program to solve this:
max_result = None
max_x = 0
max_y = 0
for x in range(0, 101):
for y in range(0, 101):
result = fun_A(x, y)
if max_result is None or result > max_result:
max_result = result
max_x = x
max_y = y
print(f"x={max_x} and y={max_y} produced the maximum result of {max_result}")

How do I convert this such that numpy works on the entire list at a time?

Works fine using a single element/variable
import math
import numpy as np
from numpy import interp
Works fine for single value
t = 25
minVal = 0
maxVal = 100
Change the range from 0 to 100 to 0 to 1 using numpy
newT = interp(t ,[minVal,maxVal],[0,1])
print newT
value = math.sin(newT * math.pi / 2)
print value,'value'
>>0.25
>>0.382683432365 value
The above code worked fine and gave me the result for a single variable. Now I want numpy to do the same thing using lists as inputs, as numpy can work on the entire list, as I've used in other cases. How do I translate the above line to get numpy to operate on the entire list?
Now to operate on a list at the same time. I can't get the lines below to work. How do I use interp on the entire lists? How do I do the math on the entire list?
t= [25,25,25]
minVal = [0,0,0]
maxVal = [100,100,100]
converedMinVal = [0,0,0]
converedMaxVal = [1,1,1]
Change the range from 0 to 100 to 0 to 1 using numpy
newT = interp(t ,[minVal,maxVal],[converedMinVal,converedMaxVal])
value = math.sin(newT * math.pi / 2)
print value,'value'
The only thing that needs to be changed is to make t an array and (as #hpaulj noted) use np.sin() rather than math.sin(), e.g.:
import numpy as np
import math
t = [10,20,30]
minVal = 0
maxVal = 100
converedMinVal = 0
converedMaxVal = 1
newT = np.interp(t ,[minVal,maxVal],[converedMinVal,converedMaxVal])
value = np.sin(newT * math.pi / 2)
print value
... for which the output is:
[ 0.15643447 0.30901699 0.4539905 ]
(EDIT)
It appears that you may want t[0] to be interpolated in [minVal[0], maxVal[0]] and t[1] to be interpolated in [minVal[1], maxVal[1]] etc. In that case, you could use scipy.interpolate.interp2d(), which creates a function that interpolates in two dimensions, e.g. as follows:
import numpy as np
import scipy.interpolate as spi
import math
t= [10,20,30]
minVal = [0, 11, 15]
maxVal = [100, 200, 500]
x = list(range (len(t)))*2
y = minVal + maxVal
converedMinVal = [0, 2, 10]
converedMaxVal = [1, 8, 20]
z = converedMinVal + converedMaxVal
f = spi.interp2d(x, y, z)
newT = np.diag(f(range(len(t)),t))
value = np.sin(newT * math.pi / 2)
print value
The function f() interpolates at all combinations of its two vector parameters and returns a matrix so if you only want interpolation at the time points in t, then you have to take the diagonal of that matrix, as I have done here.
I should note that the scipy approach is doing a lot more computation than is really necessary to solve this problem, but that might not be an issue in your case.
I would write a function to iterate through those lists and provide you with the values.
def get_values(t_vals, minVals, maxVals):
values = []
for i in range(len(t_vals)):
newT = interp(t_vals[i], [minVals[i], maxVals[i]], [0,1])
value = math.sin(newT * math.pi / 2)
values += value
return values

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.

Function with two different length variables

I'm trying to create a function, but it involves two variables of different lengths. My setup is as follows:
import pandas as pd
import numpy as np
u = np.random.normal(0,1,50)
t = 25
x = t*u/(1-u)
x = np.sort(x, axis=0)
theta = list(range(1, 1001, 1)
theta = np.divide(theta, 10) # theta is now 1000 numbers, going from 0.1 to 100
fx = np.zeros(1000)*np.nan
fx = np.reshape(fx, (1000,1))
I want my function to be the following:
def function(theta):
fx = 50/theta - 2 * np.sum(1/(theta + x))
return fx
but it won't work because theta is length 1000 and x is length 50. I want it to work iteratively for each theta, and for the part at the end:
np.sum(1/(theta + x)
I want it to add the single theta to each of the fifty numbers in x. If I were to do this once, it would look like:
fx[0] = 50/theta[0] - 2 * np.sum(1/(theta[0] + x))
I can get this to work with a "for" loop, but I eventually need to input this into a maximum likelihood function so using that won't work. Any thoughts?
The critical piece to 'vectorize' your function in not just 1D, but 2D is meshgrid. See below and print xv,yv to understand it's workings.
import numpy as np
u = np.random.normal(0,1,50)
t = 25
x = t*u/(1-u)
x = np.sort(x, axis=0)
theta = np.array( range(1, 1001, 1))
theta = theta/10.0 # theta is now 1000 numbers, going from 0.1 to 100
def function(x,theta):
fx = 50/theta - 2 * np.sum(1/(theta + x))
return fx
xv, tv = np.meshgrid(x,theta)
print function(xv,tv)
output:
[[-6582.19087928 -6582.19087928 -6582.19087928 ..., -6582.19087928
-6582.19087928 -6582.19087928]
[-6832.19087928 -6832.19087928 -6832.19087928 ..., -6832.19087928
-6832.19087928 -6832.19087928]
[-6915.52421261 -6915.52421261 -6915.52421261 ..., -6915.52421261
-6915.52421261 -6915.52421261]
...,
[-7081.68987727 -7081.68987727 -7081.68987727 ..., -7081.68987727
-7081.68987727 -7081.68987727]
[-7081.69037878 -7081.69037878 -7081.69037878 ..., -7081.69037878
-7081.69037878 -7081.69037878]
[-7081.69087928 -7081.69087928 -7081.69087928 ..., -7081.69087928
-7081.69087928 -7081.69087928]]
You might be interested in Numba.
The #vectorize decorator allow you to define a function on a scalar and use it on an array.
from numba import vectorize
import pandas as pd
import numpy as np
u = np.random.normal(0,1,50)
t = 25
x = t*u/(1-u)
x = np.sort(x, axis=0)
theta = list(range(1, 1001, 1))
theta = np.divide(theta, 10) # theta is now 1000 numbers, going from 0.1 to 100
#vectorize
def myFunction(theta):
fx = 50/theta - 2 * np.sum(1/(theta + x))
return fx
myFunction(theta)
If you want to trust the function, you can run the following code.
theta = 1
print(50/theta - 2 * np.sum(1/(theta + x)))
theta = 2
print(50/theta - 2 * np.sum(1/(theta + x)))
print(myFunction(np.array([1,2])))
Output :
21.1464816231
32.8089699838
[ 21.14648162 32.80896998]
By the way, I think it is very optimized so it can be useful for your statistical calculations (#jit decorator seems very powerful).

Categories