import numpy as np

"""
Explanation: Crash Course In Linear Algebra For Data Scientists
Preamble
This notebook was made for the purposes of quickly introducing the theoretical groundwork of Linear Algebra for Data Scientists.
To that end, we will be primarily making the computer do the hard work of doing the tedious calculations for us.
Specifically, this notebook will be using numpy as the backend for the computations.
This notebook will just ensure that your environment is loaded and all basic operations are supported.
Contents

Basic Linear Algebra Operations
Vector Spaces
Inverse Matrix Theorem
Regression and PCA
L_1 and L_2 norms from Linear Algebra.
Graph Representations

Aim of this notebook.
This notebook simply aims to make sure you have the basic environment set up to work with the rest of the notebooks.
End of explanation
"""


# Create numpy arrays like this.

print("A 1 row by 4 column numpy matrix\n{}".format(np.array(range(4))))

# Create a 2 by 2 matrix like this.

A = np.array(range(4)).reshape(2,2) # Reshape into an array of two rows and 2 columns.
print("A 2 row by 2 column numpy matrix\n{}".format(A))

# Create a 3 by 2 matrix.

A = np.array(range(6)).reshape(3,2)
print("A 3 row by 2 column numpy matrix\n{}".format(A))

# Note the ordering of how it reshapes.  It places items item by item until the row is filled.

# Be careful to match the number of elements to the shape you give to reshape!
np.array(range(6)).reshape(2,4) # Size of the inputs must equal the product of the dimensions.

"""
Explanation: Creating and shaping numpy arrays.
End of explanation
"""


# Create a (1,3) vector
np.array(range(3)).reshape(3,1)

# You can use this to create larger tensors, but we will not discuss tensors further in this tutorial.

print("A tensor!\n{}".format(np.array(range(24)).reshape(2,3,4)))

# Golly gee whiz!  Looks like two 3 by 4 matrices!

"""
Explanation: For our purposes, a matrix will be a numpy array of shape (m, n) where m, n &gt; 0 and are integers.  The matrix may consist of fractions, floats, or complex numbers.
A vector will be a matrix of shape (m, 1).
Algebraically speaking a matrix of integers is a module and is a generalization of linear Algebra.  We will not speak of such things again.  If a matrix of integers is provided, presume we meant either floats, fractions, or complex numbers as deduced from context.
End of explanation
"""


A = np.array([2,3,4,5]).reshape(2,2)
2 * A

"""
Explanation: Numpy supports broadcasting of operations.
End of explanation
"""


# Numpy dynamically casts to floats, etc.
0.5 * A

# Careful to multiply matrices, vectors, etc with the np.matmul operation.
A = np.array(range(4)).reshape(2,2)
b = np.array([2,5]).reshape(2,1)

element_wise_multiplication = A * b
matrix_multiplication = np.matmul(A, b)

print("Element-wise multiplication:\nA .* b = \n{}\n".format(element_wise_multiplication))
print("Matrix-Multiplication:\nA * b = \n{}".format(matrix_multiplication))

"""
Explanation: This is analagous to scalar multiplication with matrices.
2 * [[2,3],[4,5]] = [[4,6],[8,10]]
End of explanation
"""


# Take a Matrix and use it's .transpose method.
print(A.transpose())

"""
Explanation: Transpositions of Matrices.
End of explanation
"""


a = np.array([1,2]).reshape(2,1) # Or declare them like this in numpy.
print(a)

"""
Explanation: We will frequently write vectors as $[1,2]^T$ so we can write them in line.
End of explanation
"""


# Fix vectors u, v, w
# Fix scalars a, b

u = np.array([2,3,4]).reshape(3,1)
v = np.array([-2,3,4.5]).reshape(3,1)
w = np.array([-3.2, 5, 12]).reshape(3,1)
zero = np.array([0,0,0]).reshape(3,1)

a = 3.2
b = 4

print("Check associativity:\n {} \n== \n{}\n True!".format(u + (v + w),(u + v) + w))
print("Check commutativity:\n {}\n ==\n {}".format(u + v, v + u))
print("Check addition has an identity element:\n {}\n+\n{}\n==\n{}".format(v, -v, zero))
print("You're getting the picture...")

"""
Explanation: Basic Axioms of Linear Algebra
Taken from the Ikkipedia
Vector spaces
The main structures of linear algebra are vector spaces. A vector space over a field F (often the field of the real numbers) is a set V equipped with two binary operations satisfying the following axioms. Elements of V are called vectors, and elements of F are called scalars. The first operation, vector addition, takes any two vectors v and w and outputs a third vector v + w. The second operation, scalar multiplication, takes any scalar a and any vector v and outputs a new vector av. The operations of addition and multiplication in a vector space must satisfy the following axioms.[15] In the list below, let u, v and w be arbitrary vectors in V, and a and b scalars in F.
Axiom   Signification

Associativity of addition u + (v + w) = (u + v) + w
Commutativity of addition u + v = v + u
Identity element of addition  There exists an element 0 ∈ V, called the zero vector, such that v + 0 = v for all v ∈ V.
Inverse elements of addition  For every v ∈ V, there exists an element −v ∈ V, called the additive inverse of v, such that v + (−v) = 0
Distributivity of scalar multiplication with respect to vector addition   a(u + v) = au + av
Distributivity of scalar multiplication with respect to field addition    (a + b)v = av + bv
Compatibility of scalar multiplication with field multiplication  a(bv) = (ab)v
Identity element of scalar multiplication 1v = v, where 1 denotes the multiplicative identity in F.

The first four axioms are those of V being an abelian group under vector addition. Elements of a vector space may have various nature; for example, they can be sequences, functions, polynomials or matrices. Linear algebra is concerned with properties common to all vector spaces.
Lets see how these look in numpy
End of explanation
"""


