Converting Matlab to Python, errors with arrays and for loops - python

I am pretty new to programming so be patient with me lol. I am trying to convert an example code of matlab to python but I am having trouble with arrays in for loops and keep getting index errors.
Here is the given MatLab Code:
clear all
close all
clc
m=100; %kg
k=1000; %N/m
c=25;
v0=0;
x0=0;
dt=0.0005;
F=1000; % N the mag of input force
f0=F/m;
w=2.5; %rad/sec input frequency
t=0:dt:10;
wn=(k/m)^0.5;% rad/sec natural frequency
ze=c/(2*(k*m)^0.5);
A=[0 1; -wn^2 -2*ze*wn];
X0=[x0;v0]; %intial conditions
for i=1:length(t)
X(:,i)=X0;
Finput=[0;f0*cos(w*t(i))];
X0=X0+A*X0*dt+dt*Finput;
end
figure,plot(t,X(1,:));
title('Displacement vs tiem')
xlabel('time (second)')
ylabel('Displacement')
grid on
figure,plot(t,X(2,:),'r');
xlabel('time (second)')
ylabel('Velocity')
My code
import numpy as np
import matplotlib.pyplot as plt
#constants
k=1000
m=100
v0=0.0
x0=0.0
f=1000
c=25
f0 = f/m
wn = np.sqrt(k/m)
w = wn*2
ze =c/(2*(k*m)**0.5)
A = np.array([[0.0,1.0],[-wn**2,-2*ze*wn]])
X0= np.array([x0,v0])
dt = 0.01
t = np.arange(0, 2.5, dt) #get values between -10 and 10 with 0.01 step and set to y
for i in range (len(t)):
print(X0)
X0[:,i]=X0 #error
print(X0)
Finput = np.array([0.0,(f0*np.cos(w*dt*i))])
X0 = X0 + A*dt*X0+dt*Finput
plt.plot(t, X0[0,:])
plt.plot(t, X0[1,:])
plt.show()
I keep getting an "IndexError: too many indices for array" for the X0[:,i]=X0 part in my for loop and am struggling to figure out why.
Many Thanks in advance for the help!

In Matlab code, X(:,i)=X0; assigns X0 to ith column of X. But your python X0[:,i]=X0 #error is assiging X0 to X0 ith column.

The first time MATLAB runs the line
X(:,i)=X0;
it creates a new variable X whose i'th column is equal to X0. In your code i is 1 when this happens but if i were > 1, MATLAB would initialise columns 1...i-1 with zeroes. After the loop is done, the code plots the data from the matrix X.
You have mistakenly translated this as X0[:,i]=X0 in your Python code, which gives an error because you're trying to assign to X0 as if it were a two-dimensional array when it's only one-dimensional.
Python and numpy don't automatically create and grow arrays when you assign to a subarray in the way that MATLAB does, so in Python you need to create the array X before the loop then either resize the array each time before you assign to the next column of it, or just initialise it with the right size when you create it - since you know how big it's going to be, i.e. len(t), do the latter - you can use np.zeros for this.
Also, in the Python code as you have posted it the line X0 = X0 + A*dt*X0+dt*Finput is outside the loop because the previous line has no indentation - Python should raise an IndentationError for this though. Conventionally you should use four spaces for each level of indentation.
After the loop in the Python code you want to plot the contents of X, not X0.

Related

When converting polar to cartesian, a slice of the pie is missing

I have done measurements on external software where I do my measurements in the cylindrical coordinates R, phi and z. However, I select one z, to make a contourplot over so I have coordinates in R and phi. To turn that into x and y, I make a 2D array of x and y with x being equal to R * cos(phi) and y to R * sin(phi). Like this:
t_xray = np.zeros((Rbins, Phibins))
t_yray = np.zeros((Rbins, Phibins))
for i in range(0, Rbins):
for j in range(0, Phibins):
t_xray[i,j] = Rray[i] * np.cos(Phiray[j])
t_yray[i,j] = Rray[i] * np.sin(Phiray[j])
with Rbins and Phibins being equal to the length of the arrays of R's and phi's. Seems like a legitimate way to get it done, right? Apparently not, as this is what my plot looks like:
Plot with slice of the pie missing. Made possible with:
plt.contourf(t_xray, t_yray, Doos_TG43, 1000, locator = ticker.LogLocator())
cbar = plt.colorbar(label = r'$\it{D}$ (cGy$\cdot$ h$^{-1}$)')
My first thought was that there was somehow a bigger leap in-between two angles that Python couldn't interpolate in-between, but when printing the array of phi's, you can see the leap between the first and last angle in the array is the same as in-between any element of the array (assuming we count k2pi + phi as phi):
[0.03141593 0.09424778 0.15707963 0.21991149 0.28274334 0.34557519
0.40840704 0.4712389 0.53407075 0.5969026 0.65973446 0.72256631
0.78539816 0.84823002 0.91106187 0.97389372 1.03672558 1.09955743
1.16238928 1.22522113 1.28805299 1.35088484 1.41371669 1.47654855
1.5393804 1.60221225 1.66504411 1.72787596 1.79070781 1.85353967
1.91637152 1.97920337 2.04203522 2.10486708 2.16769893 2.23053078
2.29336264 2.35619449 2.41902634 2.4818582 2.54469005 2.6075219
2.67035376 2.73318561 2.79601746 2.85884931 2.92168117 2.98451302
3.04734487 3.11017673 3.17300858 3.23584043 3.29867229 3.36150414
3.42433599 3.48716785 3.5499997 3.61283155 3.6756634 3.73849526
3.80132711 3.86415896 3.92699082 3.98982267 4.05265452 4.11548638
4.17831823 4.24115008 4.30398194 4.36681379 4.42964564 4.49247749
4.55530935 4.6181412 4.68097305 4.74380491 4.80663676 4.86946861
4.93230047 4.99513232 5.05796417 5.12079603 5.18362788 5.24645973
5.30929158 5.37212344 5.43495529 5.49778714 5.560619 5.62345085
5.6862827 5.74911456 5.81194641 5.87477826 5.93761012 6.00044197
6.06327382 6.12610567 6.18893753 6.25176938]
So it seems I am completely out of the loop here. Why is it as if a slice is cut from the 'pie', despite everything I just mentioned?
To summarise, I tried to see whether the problem is something with the angles, but it turns out even that doesn't help giving back the slice. I have no idea what cause a piece to go missing suddenly.

How do I fix my code so that it can be applied to functions that output arrays [typerror:invalid index to scalar variable]?

Hi I'm writing a code that applies an RK4 function to different types of ODEs. I have gotten it to work for the first function but not for the second, which is 2 eqns and outputs two answers into a vector. I was able to get the whole code to work for one that outputs a single scalar variable(f2a) but I'm not sure how to change the code so that it can still do that and this second function(fb2). I'm extremely new to coding and don't really know what I'm doing so all advice and help is welcome. Code posted below:
import numpy as np
import math
import matplotlib.pyplot as plt
#defining functions
H0=7 #initial height, meters
def f2a(t,H,k,Vin,D):
dhdt=4/(math.pi*D**2)*(Vin-k*np.sqrt(H))
return(dhdt)
def fb2(J,t):
x=J[0]
y=J[1]
dxdt=0.25*y-x
dydt=3*x-y
#X0,Y0=1,1 initial conditions
return([dxdt,dydt])
#x0 and y0 are initial conditions
def odeRK4(function,tspan,R,h,*args):
#R is vector of inital conditions
x0=R[0]
y0=R[1]
#writing statement for what to do if h isnt given/other thing
if h==None:
h=.01*(tspan[1]-tspan[0])
elif h> tspan[1]-tspan[0]:
h=.01*(tspan[1]-tspan[0])
else:
h=h
#defining the 2-element array (i hope)
#pretty sure tspan is range of t values
x0=tspan[0] #probably 0 if this is meant for time
xn=tspan[1] #whatever time we want it to end at?
#xn is final x value-t
#x0 is initial
t_values=np.arange(x0,21,1) #0-20
N=len(t_values)
y_val=np.zeros(N)
y_val[0]=y0
#I am trying to print all the Y values into this array
for i in range(1,N):
#rk4 method
#k1
t1=t_values[i-1] #started range # 1, n-1 starts at 0
y1=y_val[i-1]
k1=function(t1,y1,*args)
#k2
t2=t_values[i-1]+0.5*h
y2=y_val[i-1]+0.5*k1*h
k2=function(t2,y2,*args)
#k3
t3=t_values[i-1]+0.5*h
y3=y_val[i-1]+0.5*k2*h
k3=function(t3,y3,*args)
#k4
t4=t_values[i-1]+h
y4=y_val[i-1]+h*k3
k4=function(t4,y4,*args)
y_val[i]=y_val[i-1]+(1/6)*h*(k1+2*k2+2*k3+k4)
#this fills the t_val array and keeps the loop going
a=np.column_stack((t_values,y_val))
print('At time t, Y= (t on left,Y on right)')
print(a)
plt.plot(t_values,y_val)
print('For 3A:')
#k=10, told by professor bc not included in instructions
odeRK4(f2a, [0,20],[0,7], None, 10,150,7)
print('for 3B:')
odeRK4(fb2,[0,20],[1,1],None)

Sum values from numpy array if condition on value in another array is met

I'm facing a problem with vectorizing a function so that it applies efficiently on a numpy array.
My program entries :
A pos_part 2D Array of Nb_particles lines, 3 columns (basicaly x,y,z coordinates, only z is relevant for the part that bothers me) Nb_particles can up to several hundreds of thousands.
An prop_part 1D array with Nb_particles values. This part I got covered, creation is made with some nice numpy functions ; I just put here a basic distribution that ressembles real values.
A z_distances 1D Array, a simple np.arange betwwen z=0 and z=z_max.
Then come the calculation that takes time, because where I can't find a way to do things properply with only numpy operation of arrays. What i want to do is :
For all distances z_i in z_distances, sum all values from prop_part if corresponding particle coordinate z_particle < z_i. This would return a 1D array the same length as z_distances.
My ideas so far :
Version 0, for loop, enumerate and np.where do retrieve the index of values that I need to sum. Obviously quite long.
Version 1, using a mask on a new array (combination of z coordinates and particle properties), and sum on the masked array. Seems better than v0
Version 2, another mask and a np.vectorize, but i understand it's not efficient as vectorize is basicaly a for loop. Still seems better than v0
Version 3, I'm trying to use mask on a function that can I directly apply to z_distances, but it's not working so far.
So, here I am. There is maybe something to do with a sort and a cumulative sum, but I don't know how to do this, so any help would be greatly appreciated. Please find below the code to make things clearer
Thanks in advance.
import numpy as np
import time
import matplotlib.pyplot as plt
# Creation of particles' positions
Nb_part = 150_000
pos_part = 10*np.random.rand(Nb_part,3)
pos_part[:,0] = pos_part[:,1] = 0
#usefull property creation
beta = 1/1.5
prop_part = (1/beta)*np.exp(-pos_part[:,2]/beta)
z_distances = np.arange(0,10,0.1)
#my version 0
t0=time.time()
result = np.empty(len(z_distances))
for index_dist, val_dist in enumerate(z_distances):
positions = np.where(pos_part[:,2]<val_dist)[0]
result[index_dist] = sum(prop_part[i] for i in positions)
print("v0 :",time.time()-t0)
#A graph to help understand
plt.figure()
plt.plot(z_distances,result, c="red")
plt.ylabel("Sum of particles' usefull property for particles with z-pos<d")
plt.xlabel("d")
#version 1 ??
t1=time.time()
combi = np.column_stack((pos_part[:,2],prop_part))
result2 = np.empty(len(z_distances))
for index_dist, val_dist in enumerate(z_distances):
mask = (combi[:,0]<val_dist)
result2[index_dist]=sum(combi[:,1][mask])
print("v1 :",time.time()-t1)
plt.plot(z_distances,result2, c="blue")
#version 2
t2=time.time()
def themask(a):
mask = (combi[:,0]<a)
return sum(combi[:,1][mask])
thefunc = np.vectorize(themask)
result3 = thefunc(z_distances)
print("v2 :",time.time()-t2)
plt.plot(z_distances,result3, c="green")
### This does not work so far
# version 3
# =============================
# t3=time.time()
# def thesum(a):
# mask = combi[combi[:,0]<a]
# return sum(mask[:,1])
# result4 = thesum(z_distances)
# print("v3 :",time.time()-t3)
# =============================
You can get a lot more performance by writing your first version completely in numpy. Replace pythons sum with np.sum. Instead of the for i in positions list comprehension, simply pass the positions mask you are creating anyways.
Indeed, the np.where is not necessary and my best version looks like:
#my version 0
t0=time.time()
result = np.empty(len(z_distances))
for index_dist, val_dist in enumerate(z_distances):
positions = pos_part[:, 2] < val_dist
result[index_dist] = np.sum(prop_part[positions])
print("v0 :",time.time()-t0)
# out: v0 : 0.06322097778320312
You can get a bit faster if z_distances is very long by using numba.
Running calc for the first time usually creates some overhead which we can get rid of by running the function for some small set of `z_distances.
The below code achieves roughly a factor of two speedup over pure numpy on my laptop.
import numba as nb
#nb.njit(parallel=True)
def calc(result, z_distances):
n = z_distances.shape[0]
for ii in nb.prange(n):
pos = pos_part[:, 2] < z_distances[ii]
result[ii] = np.sum(prop_part[pos])
return result
result4 = np.zeros_like(result)
# _t = time.time()
# calc(result4, z_distances[:10])
# print(time.time()-_t)
t3 = time.time()
result4 = calc(result4, z_distances)
print("v3 :", time.time()-t3)
plt.plot(z_distances, result4)

Problems using numpy.piecewise

1. The core problem and question
I will provide an executable example below, but let me first walk you through the problem first.
I am using solve_ivp from scipy.integrate to solve an initial value problem (see documentation). In fact I have to call the solver twice, to once integrate forward and once backward in time. (I would have to go unnecessarily deep into my concrete problem to explain why this is necessary, but please trust me here--it is!)
sol0 = solve_ivp(rhs,[0,-1e8],y0,rtol=10e-12,atol=10e-12,dense_output=True)
sol1 = solve_ivp(rhs,[0, 1e8],y0,rtol=10e-12,atol=10e-12,dense_output=True)
Here rhs is the right hand side function of the initial value problem y(t) = rhs(t,y). In my case, y has six components y[0] to y[5]. y0=y(0) is the initial condition. [0,±1e8] are the respective integration ranges, one forward and the other backward in time. rtol and atol are tolerances.
Importantly, you see that I flagged dense_output=True, which means that the solver does not only return the solutions on the numerical grids, but also as interpolation functions sol0.sol(t) and sol1.sol(t).
My main goal now is to define a piecewise function, say sol(t) which takes the value sol0.sol(t) for t<0 and the value sol1.sol(t) for t>=0. So the main question is: How do I do that?
I thought that numpy.piecewise should be tool of choice to do this for me. But I am having trouble using it, as you will see below, where I show you what I tried so far.
2. Example code
The code in the box below solves the initial value problem of my example. Most of the code is the definition of the rhs function, the details of which are not important to the question.
import numpy as np
from scipy.integrate import solve_ivp
# aux definitions and constants
sin=np.sin; cos=np.cos; tan=np.tan; sqrt=np.sqrt; pi=np.pi;
c = 299792458
Gm = 5.655090674872875e26
# define right hand side function of initial value problem, y'(t) = rhs(t,y)
def rhs(t,y):
p,e,i,Om,om,f = y
sinf=np.sin(f); cosf=np.cos(f); Q=sqrt(p/Gm); opecf=1+e*cosf;
R = Gm**2/(c**2*p**3)*opecf**2*(3*(e**2 + 1) + 2*e*cosf - 4*e**2*cosf**2)
S = Gm**2/(c**2*p**3)*4*opecf**3*e*sinf
rhs = np.zeros(6)
rhs[0] = 2*sqrt(p**3/Gm)/opecf*S
rhs[1] = Q*(sinf*R + (2*cosf + e*(1 + cosf**2))/opecf*S)
rhs[2] = 0
rhs[3] = 0
rhs[4] = Q/e*(-cosf*R + (2 + e*cosf)/opecf*sinf*S)
rhs[5] = sqrt(Gm/p**3)*opecf**2 + Q/e*(cosf*R - (2 + e*cosf)/opecf*sinf*S)
return rhs
# define initial values, y0
y0=[3.3578528933149297e13,0.8846,2.34921,3.98284,1.15715,0]
# integrate twice from t = 0, once backward in time (sol0) and once forward in time (sol1)
sol0 = solve_ivp(rhs,[0,-1e8],y0,rtol=10e-12,atol=10e-12,dense_output=True)
sol1 = solve_ivp(rhs,[0, 1e8],y0,rtol=10e-12,atol=10e-12,dense_output=True)
The solution functions can be addressed from here by sol0.sol and sol1.sol respectively. As an example, let's plot the 4th component:
from matplotlib import pyplot as plt
t0 = np.linspace(-1,0,500)*1e8
t1 = np.linspace( 0,1,500)*1e8
plt.plot(t0,sol0.sol(t0)[4])
plt.plot(t1,sol1.sol(t1)[4])
plt.title('plot 1')
plt.show()
3. Failing attempts to build piecewise function
3.1 Build vector valued piecewise function directly out of sol0.sol and sol1.sol
def sol(t): return np.piecewise(t,[t<0,t>=0],[sol0.sol,sol1.sol])
t = np.linspace(-1,1,1000)*1e8
print(sol(t))
This leads to the following error in piecewise in line 628 of .../numpy/lib/function_base.py:
TypeError: NumPy boolean array indexing assignment requires a 0 or 1-dimensional input, input has 2 dimensions
I am not sure, but I do think this is because of the following: In the documentation of piecewise it says about the third argument:
funclistlist of callables, f(x,*args,**kw), or scalars
[...]. It should take a 1d array as input and give an 1d array or a scalar value as output. [...].
I suppose the problem is, that the solution in my case has six components. Hence, evaluated on a time grid the output would be a 2d array. Can someone confirm, that this is indeed the problem? Since I think this really limits the usefulness of piecewiseby a lot.
3.2 Try the same, but just for one component (e.g. for the 4th)
def sol4(t): return np.piecewise(t,[t<0,t>=0],[sol0.sol(t)[4],sol1.sol(t)[4]])
t = np.linspace(-1,1,1000)*1e8
print(sol4(t))
This results in this error in line 624 of the same file as above:
ValueError: NumPy boolean array indexing assignment cannot assign 1000 input values to the 500 output values where the mask is true
Contrary to the previous error, unfortunately here I have so far no idea why it is not working.
3.3 Similar attempt, however first defining functions for the 4th components
def sol40(t): return sol0.sol(t)[4]
def sol41(t): return sol1.sol(t)[4]
def sol4(t): return np.piecewise(t,[t<0,t>=0],[sol40,sol41])
t = np.linspace(-1,1,1000)
plt.plot(t,sol4(t))
plt.title('plot 2')
plt.show()
Now this does not result in an error, and I can produce a plot, however this plot doesn't look like it should. It should look like plot 1 above. Also here, I so far have no clue what is going on.
Am thankful for help!
You can take a look to numpy.piecewise source code. There is nothing special in this function so I suggest to do everything manually.
def sol(t):
ans = np.empty((6, len(t)))
ans[:, t<0] = sol0.sol(t[t<0])
ans[:, t>=0] = sol1.sol(t[t>=0])
return ans
Regarding your failed attempts. Yes, piecewise excpect functions return 1d array. Your second attempt failed because documentation says that funclist argument should be list of functions or scalars but you send the list of arrays. Contrary to the documentation it works even with arrays, you just should use the arrays of the same size as t < 0 and t >= 0 like:
def sol4(t): return np.piecewise(t,[t<0,t>=0],[sol0.sol(t[t<0])[4],sol1.sol(t[t>=0])[4]])

plot a huge amount of data points

I have encountered a strange problem: when I store a huge amount of data points from a nonlinear equation to 3 arrays (x, y ,and z) and then tried to plot them in a 2D graph (theta-phi plot, hence its 2D).
I tried to eliminate points needed to be plotted by sampling points from every 20 data points, since the z-data is approximately periodic. I picked those points with z value just above zero to make sure I picked one point for every period.
The problem arises when I tried to do the above. I got only a very limited number of points on the graph, approximately 152 points, regardless of how I changed my initial number of data points (as long as it surpassed a certain number of course).
I suspect that it might be some command I use wrongly or the capacity of array is smaller then I expected (seems unlikely), could anyone help me find out where is the problem?
def drawstaticplot(m,n, d_n, n_o):
counter=0
for i in range(0,m):
n=vector.rungekutta1(n, d_n)
d_n=vector.rungekutta2(n, d_n, i)
x1 = n[0]
y1 = n[1]
z1 = n[2]
if i%20==0:
xarray.append(x1)
yarray.append(y1)
zarray.append(z1)
for j in range(0,(m/20)-20):
if (((zarray[j]-n_o)>0) and ((zarray[j+1]-n_o)<0)):
counter= counter +1
print zarray[j]-n_o,counter
plotthetaphi(xarray[j],yarray[j],zarray[j])
def plotthetaphi(x,y,z):
phi= math.acos(z/math.sqrt(x**2+y**2+z**2))
theta = math.acos(x/math.sqrt(x**2 + y**2))
plot(theta, phi,'.',color='red')
Besides, I tried to apply the code in the following SO question to my code, I want a very similar result except that my data points are not randomly generated.
Shiuan,
I am still investigating your problem, how ever a few notes:
Instead of looping and appending to an array you could do:
select every nth element:
# inside IPython console:
[2]: a=np.arange(0,10)
In [3]: a[::2] # here we select every 2nd element.
Out[3]: array([0, 2, 4, 6, 8])
so instead of calcultating runga-kutta on all elements of m:
new_m = m[::20] # select every element of m.
now call your function like this:
def drawstaticplot(new_m,n, d_n, n_o):
n=vector.rungekutta1(n, d_n)
d_n=vector.rungekutta2(n, d_n, i)
x1 = n[0]
y1 = n[1]
z1 = n[2]
xarray.append(x1)
yarray.append(y1)
zarray.append(z1)
...
about appending, and iterating over large data sets:
append in general is slow, because it copies the whole array and then
stacks the new element. Instead, you already know the size of n, so you could do:
def drawstaticplot(new_m,n, d_n, n_o):
# create the storage based on n,
# notice i assumed that rungekutta, returns n the size of new_m,
# but you can change it.
x,y,z = np.zeros(n.shape[0]),np.zeros(n.shape[0]), np.zeros(n.shape[0])
for idx, itme in enumerate(new_m): # notice the function enumerate, make it your friend!
n=vector.rungekutta1(n, d_n)
d_n=vector.rungekutta2(n, d_n, ite,)
x1 = n[0]
y1 = n[1]
z1 = n[2]
#if i%20==0: # we don't need to check for the 20th element, m is already filtered...
xarray[idx] = n[0]
yarray[idx] = n[1]
zarray[idx] = n[2]
# is the second loop necessary?
if (((zarray[idx]-n_o)>0) and ((zarray[j+1]-n_o)<0)):
print zarray[idx]-n_o,counter
plotthetaphi(xarray[idx],yarray[idx],zarray[idx])
You can use the approach suggested here:
Efficiently create a density plot for high-density regions, points for sparse regions
e.g. histogram where you have too many points and points where the density is low.
Or also you can use rasterized flag for matplotlib, which speeds up matplotlib.

Categories

Resources