# Without Numpy, to get the square root on a list of numbers:

from math import *

x1 = [23,12,45]
for y, z in enumerate(x1):
    Ex1 = sqrt(z)
    print Ex1

"""
Explanation: <span style="color: #B40486">BASIC PYTHON FOR RESEARCHERS</span>
by Megat Harun Al Rashid bin Megat Ahmad
last updated: April 14, 2016

<span style="color: #29088A">6. Numerical Python</span>
In Python, a list is considered as an array and operating on each element of the list can be done by accessing them sequentially (e.g. by using <span style="color: #0000FF">$for$</span> loop function).
End of explanation
"""


from numpy import *

# With Numpy, to get the square root on a list of numbers:

# Creating the Python list
x1 = [23,12,45]

# Transforming the Python list into NumPy array
xnp = array(x1)

# Here we use the available NumPy square root function
Ex1 = sqrt(xnp)

print xnp
print Ex1

"""
Explanation: With <span style="color: #0000FF">$NumPy$</span>, elements in the list can be operated directly without the need to access them sequentially. This can be done by transforming the Python list into a <span style="color: #0000FF">$NumPy$</span> array. Python utilizes this <span style="color: #0000FF">$NumPy$</span> library as part of its fundamental package for scientific computing. Its major function is to allow easy operations especially on and between homogeneous multi-dimensional arrays. Available features in <span style="color: #0000FF">$NumPy$</span> can be accessed by importing the library.
End of explanation
"""


# Array operation on real and imaginary parts to obtain complex conjugate 

i = array([12.7,9.3,0.8])
j = array([4.5j,2.7j,3.1j])

print (i + j)*(i + j).conjugate()

"""
Explanation: Elements of <span style="color: #0000FF">$NumPy$</span> array are not separated by comma when printed. They are separated only by space. 
Performing mathematical operation between arrays requires part of these arrays to be of the same size (e.g. 5 x 10 array can be multiplied with another 5 x 10 array or 5 x 1 array). This will be explored further).
End of explanation
"""


import numpy as np

# Creating a one-dimensional array with sequential integer values

a = np.arange(15)
a

"""
Explanation: 6.1 Features of NumPy Array
Creating an array using <span style="color: #0000FF">$NumPy$</span> can be done in several ways. In all the following examples,  <span style="color: #0000FF">$NumPy$</span> is imported as <span style="color: #0000FF">$np$</span>. Therefore <span style="color: #0000FF">$NumPy$</span> can be initially accessed by using <span style="color: #0000FF">$np.{function}$</span>. This allows the separate usage of similar wordings <span style="color: #0000FF">$NumPy$</span> and Python <span style="color: #0000FF">$math$</span> function like <span style="color: #0000FF">$sqrt$</span>. It is also possible to use <span style="color: #0000FF">$NumPy$</span> function names directly that have no equivalent in Python functions.
End of explanation
"""


'''Below means that the g array is going to contains range of elements with minimum
value of 10 (inclusive of 10) up to a maximum value of 50 (exclusive of or less than 50) and with
an increment of 5'''

g = np.arange(10,50,5)     
print g

# To include 50, just set 50 to 51

g = np.arange(10,51,5)     
print g

"""
Explanation: This create a one dimensional array with 15 elements starting from 0 to 14. 
The range and increment of the array elements during array creation can be specified by using the (min, max, add) arguments in the <span style="color: #0000FF">$arange$</span> function.
End of explanation
"""


x = np.linspace(0,2*np.pi,20)         # note: np.pi is used instead of just pi
print x

# Checking the inclusivity
print x.min()
print x.max()
print x.size

"""
Explanation: Another useful array creating function is <span style="color: #0000FF">$linspace$</span> which allows specifying (inclusive both) the minimum and maximum values and the number of array elements.
End of explanation
"""


# Creating a two-dimensional 3 x 5 array from b 

b = a.reshape(3,5)
b

"""
Explanation: To create a higher dimensional array, we can use any one of these functions and then use the <span style="color: #0000FF">$reshape$</span> function. Let reshapes variable a into a two dimensional 3 x 5 array.
End of explanation
"""


c = np.arange(27).reshape(3,3,3)
print c

"""
Explanation: Shaping a three dimensional 3 x 3 x 3 array:
End of explanation
"""


print c.shape        # shape of array c
print b.ndim         # dimension of array b
print a.size         # size of array a or the number of elements in array a

# mean value for the c array
np.mean(c)

# mean value for b array elements in each column
np.mean(b, axis=0)

# mean value for b array elements in each row
np.mean(b, axis=1)

# average value for a all array elements
np.average(a)

# sum values for c array elements in axis 2
np.sum(c,axis=2)

"""
Explanation: We can check the shape, dimension and size of these arrays, as well as the mean and median of the arrays.
End of explanation
"""


# Finding the sin^2(x) + cos^2(x) (trigonometric identity)

np.sin(x)**2 + np.cos(x)**2         # note: np.sin and np.cos are used instead of just sin and cos

z = np.linspace(1,5.347,20)

x*z         # note:  arrays of the same size and dimension

a1 = np.linspace(1,8.75,12).reshape(3,4)       # a1 is 3 x 4 array
a1

b1 = np.linspace(1,4,4)        # b1 is 1 x 4 array
b1

a1*b1

"""
Explanation: Earlier in this tutorial, a simple mathematical operations for complex number array has been shown. <span style="color: #0000FF">$NumPy$</span> arrays can only be operated with each other when the arrays are of the same size and dimension.
End of explanation
"""


c1 = np.linspace(1,3,3)
c1

a1*c1

"""
Explanation: It is possible to operate this a1 with b1 as one of part of the array (the column) is equivalent in size.
|array|row|column|
|---|---|---|
|a1|$$3$$|$$4$$|
|b1|$$1$$|$$4$$|
Let c1 be a 1 x 3 array:
End of explanation
"""


a1.T*c1


"""
Explanation: The interpreter produces an error as neither row nor column of a1 is equivalent to c1.
|array|row|column|
|---|---|---|
|a1|$$3$$|$$4$$|
|c1|$$1$$|$$3$$|
It is still possible to operate a1 with c1. This can be done by transposing a1 (a1 now becomes 4 x 3 array, so the column size is the same as c1 column size).
End of explanation
"""


y = np.ones((10,10))
print y
z = np.zeros((5,5))
print z

i, j = np.mgrid[0:4, 0:4]       # similar to meshgrid in MATLAB

i

j

"""
Explanation: Other type of arrays are array with ones and zeroes. These are flexible arrays and can be used for many purposes e.g. indexing collision particles in two dimensions. There is also the <span style="color: #0000FF">$mgrid$</span> function that is similar to meshgrid in MATLAB.
End of explanation
"""


m, n = np.mgrid[0:10, 0:10]
(m > n)*1

"""
Explanation: <span style="color: #F5DA81; background-color: #610B4B">Exercise 6.1</span>: Create this 2-dimensional array using NumPy:
$$\left[ \begin{array}{cc}
0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\
1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\
1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\
1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\
1 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0\
1 & 1 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0\
1 & 1 & 1 & 1 & 1 & 1 & 0 & 0 & 0 & 0\
1 & 1 & 1 & 1 & 1 & 1 & 1 & 0 & 0 & 0\
1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 0 & 0\
1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 0\end{array} \right]$$
End of explanation
"""


# Slicing
x = np.linspace(0,2*np.pi,20)
print x
print x[3:5] 

y = np.arange(15).reshape(3,5)
print y
print y[1]                   # the whole row of index 1 

z = y[0:2,1:4]               # assigning to variable
z

y[0:2,1:4] = 0               # reassigned
y

# reassigned with new array
y[0:2,1:4] = np.array([[45,89,12],[12,73,69]])       
y

"""
Explanation: <span style="color: #0000FF">$NumPy$</span> array can be sliced, extracted and reassigned similar to the operations on Python sequences.
End of explanation
"""


X = np.array([n**np.e for n in range(5)])       # note: np.e represent exponential constant
X

# i is row index and j is column index
W = np.array([[j+i*3 for j in range(3)] for i in range(3)])
W

"""
Explanation: There are many other ways to create <span style="color: #0000FF">$NumPy$</span> array, some are quite fancy e.g.:
End of explanation
"""


'''Let us build a 3-dimensional 5 x 5 x 5 array 
containing elements created using random number'''

rand_array = np.random.rand(4,4,4)
rand_array

"""
Explanation: 6.2 File Input/Output
<span style="color: #0000FF">$NumPy$</span> array can be saved into a binary file which later can be loaded. 1-D and 2-D arrays can also be saved in text format.
End of explanation
"""


np.save("Tutorial6/random_3D_array.npy", rand_array)

np.load("Tutorial6/random_3D_array.npy")

rand2_array = np.random.rand(4,4)

"""
Explanation: (The default random number generator algorithm is Mersenne-Twister)
End of explanation
"""


np.savetxt("Tutorial6/random_2D_array.txt", rand2_array, delimiter=',')

"""
Explanation: The coming examples show how to save array into text file.
End of explanation
"""


fh_csv = open("Tutorial6/random_2D_array.txt")
fh_csvList = fh_csv.readlines()
fh_csvList

"""
Explanation: In the above example, the array rand2_array is saved as comma separated values by specifying comma as the delimiter used.
End of explanation
"""


np.savetxt("Tutorial6/random_2D_arrayF.txt", rand2_array, \
           delimiter=',', fmt='%.3f')

fh_csv1 = open("Tutorial6/random_2D_arrayF.txt")
fh_csv1List = fh_csv1.readlines()
fh_csv1List

"""
Explanation: It is possible to specify or formatted the decimal value when saving a text file.
End of explanation
"""


fromtxtFile = np.loadtxt("Tutorial6/random_2D_arrayF.txt", delimiter=',')

fromtxtFile

"""
Explanation: Similar with binary file, a text file can be loaded as an array with delimiter used specified.
End of explanation
"""


# Creating two arrays with random numbers, M1 and M2
M1 = np.random.rand(3,3)*np.linspace(1,36,9).reshape(3,3)
M2 = np.random.rand(3,3)*np.linspace(1,2*np.e,9).reshape(3,3)
M1, M2

# dot product
np.dot(M1,M2)

N1 = np.arange(2,8,2)
N1

np.dot(M1,N1)

np.dot(N1,N1)

"""
Explanation: 6.3 Matrix Operations
What we have seen previously were scalar array operations. <span style="color: #0000FF">$NumPy$</span> array can also undergoes matrix operations. <span style="color: #0000FF">$NumPy$</span> has special functions for matrix operations.
End of explanation
"""


W1 = np.matrix(M1)
W1

Y = np.matrix(N1).T  
Y

W2 = np.matrix(M2)

W1*W2                # instead of using np.dot() function

"""
Explanation: Standard arithmetic operators (+, -, *) can be used for matrix operations by casting the <span style="color: #0000FF">$NumPy$</span> array as of type matrix.
End of explanation
"""


np.shape(W1), np.shape(Y)

Y + W1*Y

"""
Explanation: Matrix operations must adhered to standard matrix agebra and it is good practice to check the shape of the matrices before any operations.
End of explanation
"""


# Finding determinant

np.linalg.det(W1)

# Inverse matrix

np.linalg.inv(W1)

np.linalg.inv(W1)*W1

"""
Explanation: A few others mathematical functions on matrix include:
End of explanation
"""


