Python for Quantum Mechanics: Numpy Array Creation & Manipulation

Python for Quantum Mechanics: Numpy Array Creation & Manipulation#

from IPython.display import YouTubeVideo
YouTubeVideo('JkAW8I7GVnw',width=700, height=400)
import numpy as np

Methods To Create Numpy Arrays#

There are various methods we can use to create arrays of different shapes with different data entries.

np.arange(start,stop,step,dtype)

  • Gives elements between start and stop, including start, in intervals of size step

arr = np.arange(5,21,2)
print(arr)
[ 5  7  9 11 13 15 17 19]

np.linspace(start,stop,num,dtype)

  • This gives num amount of evenly spaced elements including, and inbetween, start and stop.

arr = np.linspace(5,19,8)
print(arr)
[ 5.  7.  9. 11. 13. 15. 17. 19.]

np.zeros((r,c),dtype)

  • Gives a matrix of zeros that is of shape (r,c)

arr = np.zeros((3,4),float)
print(arr)
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]

np.ones((r,c),dtype)

  • Gives a matrix of ones that is of shape (r,c)

arr = np.ones((3,4),float)
print(arr)
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]

np.diag(v,k=0)

  • Gives a matrix of zeros with elements of v(array-like) on the diagonal

  • k specifies which diagonal

  • k=0 is the main diagonal, and is set if k is unspecified

arr = np.diag([1,2,3],k=0)
print(arr)
[[1 0 0]
 [0 2 0]
 [0 0 3]]
arr = np.diag([1,2,3],k=3)
print(arr)
[[0 0 0 1 0 0]
 [0 0 0 0 2 0]
 [0 0 0 0 0 3]
 [0 0 0 0 0 0]
 [0 0 0 0 0 0]
 [0 0 0 0 0 0]]

np.identity(n,dtype)

  • Gives an nxn identity matrix

arr = np.identity(3,float)
print(arr)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

np.eye(N,M,k,dtype)

  • Gives an NxN identity matrix if M is unspecified

  • Otherwise gives and NxM matrix with ones on a diagonal

  • k specifies which diagonal

arr = np.eye(4,5,k=0)
print(arr)
[[1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0.]]
arr = np.eye(4,5,k=1, dtype=int)
print(arr)
[[0 1 0 0 0]
 [0 0 1 0 0]
 [0 0 0 1 0]
 [0 0 0 0 1]]

x,y = np.mgrid(array-like,array-like)

x,y = np.mgrid[0:4,0:6]

print(x,'\n')
print(y)
[[0 0 0 0 0 0]
 [1 1 1 1 1 1]
 [2 2 2 2 2 2]
 [3 3 3 3 3 3]] 

[[0 1 2 3 4 5]
 [0 1 2 3 4 5]
 [0 1 2 3 4 5]
 [0 1 2 3 4 5]]

We can also create arrays filled with random numbers, in [0,1), using \(np.random.rand(shape)\).

np.random.rand(rows,columns)

arr = np.random.rand(3,4)
print(arr)
[[0.55948793 0.41369894 0.00174296 0.04175902]
 [0.02480888 0.65165339 0.69159216 0.06557134]
 [0.75554221 0.04371706 0.76314129 0.76970817]]

If we multiply it by an integer(or float) \(n\), we’ll obtain a random distribution over the interval [0,n).

arr = 100*np.random.rand(3,4)
print(arr, '\n')
[[ 4.21198209 29.54014986 72.9926029  90.17257111]
 [97.47574048 98.94689434 89.08261852 35.08825765]
 [40.21912357 19.12354169 40.30703767 76.32807857]] 

To obtain a random array of integers we can then round the array, then cast the elements as integers, as follows

arr = np.round(arr).astype(int)
print(arr)
[[ 4 30 73 90]
 [97 99 89 35]
 [40 19 40 76]]

Non-Numerical Data Types#

Numpy also supports non-numerical data:

arr = np.array(['a','bcd'])
print(arr)
print(arr.dtype)
['a' 'bcd']
<U3

We see above that the data type is the minimum data type required to store the largest element in the array. In the above case, a string with three elements, ‘bcd’. This is again demonstrated below:

arr = np.array(['a','bcd','efghijk'])
print(arr)
print(arr.dtype)
['a' 'bcd' 'efghijk']
<U7

Instead of strings we can also cast the data as characters

arr = np.array(['efghijk'], dtype='c')
print(arr)
[[b'e' b'f' b'g' b'h' b'i' b'j' b'k']]

Similarly you might also want to add multiple strings, which can be done as follows.

stringy = 'a' + 'bcd' + 'efghijk'
arr = np.array(stringy, dtype='c')
print(arr)
[b'a' b'b' b'c' b'd' b'e' b'f' b'g' b'h' b'i' b'j' b'k']

Indexing Numpy Arrays#

We can find elements in Numpy arrays in the following way

arr = np.array([1,2])
print(arr)
print(arr[0])
[1 2]
1

This is similar to lists. However, things change when we add dimensions!!

L = [[1,2],[3,4]]
arr = np.array(L)

To access data in the LIST!

print(L[1][1])
4

In the array, it is different and found as follows

print(arr,'\n')

print(arr[0,0])
print(arr[0,1])
print(arr[1,0])
print(arr[1,1])
[[1 2]
 [3 4]] 

1
2
3
4

Slicing Numpy Arrays#

arr[lower:upper:step]

Slicing is a dynamical way to extract certain parts of an array. Lets create a list and slice it up

arr = np.array([x**2 for x in range(21)]) #No reason why list comprehensions can't be used
print(arr)
[  0   1   4   9  16  25  36  49  64  81 100 121 144 169 196 225 256 289
 324 361 400]

The first thing to notice is that slicing gives views of the array but doesn’t actually remove parts of it from memory.

slic = arr[5:15:3]

print(slic)
print(arr)
[ 25  64 121 196]
[  0   1   4   9  16  25  36  49  64  81 100 121 144 169 196 225 256 289
 324 361 400]

If we leave out lower, it is automatically set to the first element. If we also leave out step, it is set to 1.

print(arr[:5])
[ 0  1  4  9 16]

If we leave out upper, it is set to the last element

print(arr[5:])
[ 25  36  49  64  81 100 121 144 169 196 225 256 289 324 361 400]
print(arr[::2])
[  0   4  16  36  64 100 144 196 256 324 400]
print(arr[:10:2])
[ 0  4 16 36 64]

Lets try to input negative indexes

print(arr[-3:])
[324 361 400]

We see that a negative number just means to count indexes from right to left.

print(arr[:-3])
[  0   1   4   9  16  25  36  49  64  81 100 121 144 169 196 225 256 289]
print(arr[::-1])
[400 361 324 289 256 225 196 169 144 121 100  81  64  49  36  25  16   9
   4   1   0]

The above example is a nice trick to reverse an array!

Lets look at slicing 2-d arrays. It works in the same way:

arr = np.random.rand(4,5)
arr = np.round(100*arr).astype(int)
print(arr)
[[27  5 21 21 79]
 [89  5 33 25 10]
 [81 21 37  5 32]
 [58 22 10 54  6]]
print(arr[0:3,0:3])
[[27  5 21]
 [89  5 33]
 [81 21 37]]
print(arr[1:4,1:4])
[[ 5 33 25]
 [21 37  5]
 [22 10 54]]
print(arr[::2,::2])
[[27 21 79]
 [81 37 32]]

Array Manipulation#

We can change arrays in various ways.

Concatenation is accomplished with \(np.concatenate(array1,array2)\)

np.concatenate((a1,a2,...),axis=0,dtype=None)

arr1 = np.array([[1,2,3],[4,5,6]])
arr2 = np.array([[7,8,9],[10,11,12]])

print(arr1,'\n')
print(arr2,'\n')

conc = np.concatenate((arr1,arr2))

print(conc)
[[1 2 3]
 [4 5 6]] 

[[ 7  8  9]
 [10 11 12]] 

[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]
conc = np.concatenate((arr1,arr2),axis=1)

print(conc)
[[ 1  2  3  7  8  9]
 [ 4  5  6 10 11 12]]

We can concatenate more than one array

arr3 = np.array([[13,14,15],[16,17,18]])

conc = np.concatenate((arr1,arr2,arr3),axis=1)

print(conc)
[[ 1  2  3  7  8  9 13 14 15]
 [ 4  5  6 10 11 12 16 17 18]]

arr.reshape(n,m)

We can reshape an array so it has different dimensions.

arr = np.array([[1,2,3],[4,5,6]])

print(arr,'\n')

arr_reshaped = arr.reshape(3,2)

print(arr_reshaped,'\n')
[[1 2 3]
 [4 5 6]] 

[[1 2]
 [3 4]
 [5 6]] 

Note that if we modify the new array, the original is also modified, in other words: \(arr\_reshaped\) is just a view of \(arr\) and not a distinct array.

arr_reshaped[0][0:] = 700

print(arr_reshaped,'\n')

print(arr,'\n')
[[700 700]
 [  3   4]
 [  5   6]] 

[[700 700   3]
 [  4   5   6]] 

arr.flatten()

We can convert an array into a one-dimensional vecotr/array using \(flatten()\)

arr = np.array([[1,2,3],[4,5,6]])

print(arr,'\n')

arr = arr.flatten()

print(arr,'\n')
[[1 2 3]
 [4 5 6]] 

[1 2 3 4 5 6] 

arr.ravel()

We can similary use \(ravel()\)

arr = np.array([[1,2,3],[4,5,6]])

print(arr,'\n')

arr = arr.ravel()

print(arr,'\n')
[[1 2 3]
 [4 5 6]] 

[1 2 3 4 5 6] 

np.hsplit(array,n)

We can split arrays into sub-arrays as follows

arr = np.array([[1,2,3],[4,5,6]])

print(arr,'\n')

arr = np.hsplit(arr,3)

print(arr,'\n')

print(arr[0],'\n')
[[1 2 3]
 [4 5 6]] 

[array([[1],
       [4]]), array([[2],
       [5]]), array([[3],
       [6]])] 

[[1]
 [4]]