#--------------------------------------------------
# Lecture 7 -- Example 2. Loops and Array Math
#--------------------------------------------------

import numpy as np

#%%
#--------------------------------------------------
# A. Array Math using Loops
#--------------------------------------------------

# Loops are very useful with arrays.

# (i) Filling an array using a loop:

my_array = np.empty(5)
for i in range(5):
    my_array[i] = i

print('my_array')
print(my_array)

# (ii) Filling a matrix using a loop:

my_matrix = np.empty((5,5))
for i in range(5):
    for j in range(5):
        my_matrix[i,j] = i+j

print('\nmy_matrix')
print(my_matrix)

# (iii) Using loops and arrays with sums

# Loops can be used to do sums and other formulas that involve sums
# (like vector and matrix operations)

vec1 = np.array([1, 2, 3, 4, 5])
vec2 = np.array([2, 4, 6, 8, 10])
matrix1 = np.arange(25).reshape(5,5)
matrix2 = np.arange(-25, 0, 1).reshape(5,5)

# Summation formula: sum_i vec1_i
this_sum = 0
for i in range(len(vec1)):
    this_sum += vec1[i]

print('\nthe sum: ', this_sum)

# Dot product formula: sum_i (vec1_i * vec2_i)
dot_product = 0
for i in range(len(vec1)):
    dot_product += vec1[i]*vec2[i]

print('\ndot product', dot_product)

# Matrix-vector product formula: sum_j (matrix1_ij * vec1_j)
mv_product = np.zeros(5) # why use zeros not empty here?
for i in range(5):
    for j in range(5):
        mv_product[i] += matrix1[i, j]*vec1[j]

print('\nmatrix-vector product', mv_product)

# Matrix-matrix product formula: sum_j (matrix1_ij * matrix2_jk)
mm_product = np.zeros((5, 5))
for i in range(5):
    for j in range(5):
        for k in range(5):
            mm_product[i, k] += matrix1[i, j]*matrix2[j, k]

print('\nmatrix-matrix product', mm_product)

# *** Practice ***
# The 2-norm of a vector is defined as the square root of the sum of the all of
# the elements squared (i.e. the distance formula):
# two_norm = sqrt( sum_i ( vec_i**2) )
# 
# Use a loop to find the norm of vec2. (answer = 14.83239697...)


#%%
#--------------------------------------------------
# B. Array Math using Numpy Functions
#--------------------------------------------------
#
# Many operations can be done on a whole array at once. 
# This is often very convenient.
#
# For examle, basic mathematics such as addition, multiplication, power are
# the same syntax as non-array variables

x = np.arange(3, 21, 3)
y = -2*x

print('\ny = -2x')
print('x = ', x)
print('y = ', y)

y = x**2
print('\ny = x**2')
print('x = ', x)
print('y = ', y)

z = x*y
print('\nz = x*y')
print('x = ', x)
print('y = ', y)
print('z = ', z)

# Array specific operations are also possible

# (i) vector-vector dot product
z = np.dot(x,y) # dot product, or np.sum(x*y)
print()
print('z = x.y')
print('x = ', x)
print('y = ', y)
print('z = ', z)

# (ii) matrix-vector dot product
A = np.eye(np.size(x)) # Eye generates an the identity matrix
z = np.dot(A, x)
print()
print('z = A.x')
print('x = ', x)
print('A = \n', A)
print('z = ', z)

# (iii) matrix-matrix dot product
A = 3*np.eye(np.size(x)) # matrix with 3 on diagonals
B = -2*np.eye(np.size(x)) # matrix with -2 on diagonals
C = np.dot(A, B)
print()
print('C = A.B')
print('A = \n', A)
print('B = \n', B)
print('C = \n', C)

# ***There are many other functions, see above links***
# * repmat: make new arrays out of existing one (repeat matrix)
# * hstack, vstack: to combine arrays
# * sort
# * eye
# * diag
# * transpose
# * etc.
