En esta tarea seran guiados paso a paso en como realizar un sistema de arrays en Python para realizar operaciones de algebra lineal.
Pero antes... (FAQ)
Como se hace en la realidad? En la practica, se usan paqueterias funcionales ya probadas, en particular numpy
, que contiene todas las herramientas necesarias para hacer computo numerico en Python
.
Por que hacer esta tarea entonces? Python
es un lenguage disenado para la programacion orientada a objetos. Al hacer la tarea desarrollaran experiencia en este tipo de programacion que les permitira crear objetos en el futuro cuando lo necesiten, y entender mejor como funciona numpy
y en general, todas las herramientas de Python
. Ademas, en esta tarea tambien aprenderan la forma de usar numpy
simultaneamente.
Como comenzar con numpy? En la tarea necesitaremos importar la libreria numpy
, que contiene funciones y clases que no son parte de Python
basico. Recuerden que Python no es un lenguage de computo cientifico, sino de programacion de proposito general. No esta disenado para hacer algebra lineal, sin embargo, tiene librerias extensas y bien probadas que permiten lograrlo. Anaconda
es una distribucion de Python
que ademas de instalarlo incluye varias librerias de computo cientifico como numpy
. Si instalaron Python
por separado deberan tambien instalar numpy
manualmente.
Antes de comenzar la tarea deberan poder correr:
In [62]:
import numpy as np
Lo que el codigo anterior hace es asociar al nombre np
todas las herramientas de la libreria numpy. Ahora podremos llamar funciones de numpy como np.<numpy_fun>
. El nombre np
es opcional, pueden cambiarlo pero necesitaran ese nombre para acceder a las funciones de numpy
como <new_name>.<numpy_fun>
. Otra opcion es solo inlcuir import numpy
, en cuya caso las funciones se llaman como numpy.<numpy_fun>
. Para saber mas del sistema de modulos pueden revisar la liga https://docs.python.org/2/tutorial/modules.html
In [63]:
x = [1,2,3]
y = [4,5,6]
x + y
Out[63]:
Vamos a construir una clase Array que incluye a las matrices y a los vectores. Desde el punto de vista computacional, un vector es una matriz de una columna. En clase vimos que conviene pensar a las matrices como transformacion de vectores, sin embargo, desde el punto de vista computacional, como la regla de suma y multiplicacion es similar, conviene pensarlos ambos como arrays, que es el nombre tradicional en programacion
Computacionalmente, que es un array? Tecnicamente, es una lista de listas, todas del mismo tamano, cada uno representando una fila (fila o columna es optativo, haremos filas porque asi lo hace numpy
, pero yo previero columnas). Por ejemplo, la lista de listas
[[1,2,3],[4,5,6]]
Corresponde a la matriz $$ \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} $$
In [64]:
B = np.array([[1,2,3], [4,5,6]]) # habiendo corrido import numpy as np
Es posible sumar matrices y multiplicarlas por escalares
In [65]:
B + 2*B # Python sabe sumar y multiplicar arrays como algebra lineal
Out[65]:
Las matrices de numpy se pueden multiplicar con la funcion matmul
dentro de numpy
In [66]:
np.matmul(B.transpose(), B) # B^t*B
Out[66]:
Los arrays the numpy pueden accesarse con indices y slices
Una entrada especifica:
In [67]:
B[1,1]
Out[67]:
Una fila entera:
In [68]:
B[1,:]
Out[68]:
Una columna entera:
In [69]:
B[:,2]
Out[69]:
Un subbloque (notar que un slice n:m
es n,n+1,...,m-1
In [70]:
B[0:2,0:2]
Out[70]:
En numpy podemos saber la dimension de un array con el campo shape
de numpy
In [71]:
B.shape
Out[71]:
Numpy es listo manejando listas simples como vectores
In [72]:
vec = np.array([1,2,3])
print(vec)
In [73]:
class Array:
"Una clase minima para algebra lineal"
def __init__(self, list_of_rows):
"Constructor"
self.data = list_of_rows
self.shape = (len(list_of_rows), len(list_of_rows[0]))
In [74]:
A = Array([[1,2,3], [4,5,6]])
A.__dict__ # el campo escondido __dict__ permite acceder a las propiedades de clase de un objeto
Out[74]:
In [75]:
A.data
Out[75]:
In [76]:
A.shape
Out[76]:
El campo data
de un Array almacena la lista de listas del array. Necesitamos implementar algunos metodos para que sea funcional como una clase de algebra lineal.
Con esto seria posible hacer algebra lineal
Para hacer esto es posible usar metodos especiales de clase __getitem
, __setitem__
, __add__
, __mul__
, __str__
. Teoricamente es posible hacer todo sin estos metodos especiales, pero, por ejemplo, es mucho mas agradable escribir A[i,j]
que A.get(i,j)
o A.setitem(i,j,newval)
que A[i,j] = newval
.
In [77]:
Array([[1,2,3], [4,5,6]])
Out[77]:
In [78]:
print(Array([[1,2,3], [4,5,6]]))
In [79]:
np.array([[1,2,3], [4,5,6]])
Out[79]:
In [80]:
print(np.array([[1,2,3], [4,5,6]]))
Por que estas diferencias? Python secretamente busca un metodo llamado __repr__
cuando un objeto es llamado sin imprimir explicitamente, y __str__
cuando se imprime con print
explicitamente. Por ejemplo:
In [81]:
class TestClass:
def __init__(self):
pass # this means do nothing in Python
def say_hi(self):
print("Hey, I am just a normal method saying hi!")
def __repr__(self):
return "I am the special class method REPRESENTING a TestClass without printing"
def __str__(self):
return "I am the special class method for explicitly PRINTING a TestClass object"
In [82]:
x = TestClass()
In [83]:
x.say_hi()
In [84]:
x
Out[84]:
In [85]:
print(x)
In [126]:
class Array:
"Una clase minima para algebra lineal"
def __init__(self, list_of_rows):
"Constructor y validador"
# obtener dimensiones
self.data = list_of_rows
nrow = len(list_of_rows)
# ___caso vector: redimensionar correctamente
if not isinstance(list_of_rows[0], list):
nrow = 1
self.data = [[x] for x in list_of_rows]
# ahora las columnas deben estar bien aunque sea un vector
ncol = len(self.data[0])
self.shape = (nrow, ncol)
# validar tamano correcto de filas
if any([len(r) != ncol for r in self.data]):
raise Exception("Las filas deben ser del mismo tamano")
# Ejercicio 1
def __repr__(self):
str2print = "Array"
for i in range(len(self.data)):
if(i==0):
str2print += str(self.data[i]) + "\n"
if(i>0):
str2print += " " + str(self.data[i]) + "\n"
return str2print
def __str__(self):
str2print = ""
for i in range(len(self.data)):
str2print += str(self.data[i]) + "\n"
return str2print
#Ejercicio2
def __getitem__(self, idx):
return self.data[idx[0]][idx[1]]
def __setitem__(self, idx, valor):
self.data[idx[0]][idx[1]] = valor
# Ejercicio 3
def zeros(x, y):
array_de_ceros = Array([[0 for col in range(y)] for row in range(x)])
return array_de_ceros
def eye(x):
array_eye = Array([[0 for col in range(x)] for row in range(x)])
for i in range(x):
for j in range(x):
if i == j:
array_eye[i,j] = 1
return array_eye
# Ejercicio 4
def transpose(self):
#Obtener dimensiones
num_row = len(self.data)
num_col = len(self.data[0])
#Crear matriz receptora
mat_transpuesta = Array([[0 for col in range(num_row)] for row in range(num_col)])
#Transponer
for i in range(num_row):
for j in range(num_col):
mat_transpuesta[j,i] = self.data[i][j]
return mat_transpuesta
def __add__(self, other):
"Hora de sumar"
if isinstance(other, Array):
if self.shape != other.shape:
raise Exception("Las dimensiones son distintas!")
rows, cols = self.shape
newArray = Array([[0. for c in range(cols)] for r in range(rows)])
for r in range(rows):
for c in range(cols):
newArray.data[r][c] = self.data[r][c] + other.data[r][c]
return newArray
elif isinstance(2, (int, float, complex)): # en caso de que el lado derecho sea solo un numero
rows, cols = self.shape
newArray = Array([[0. for c in range(cols)] for r in range(rows)])
for r in range(rows):
for c in range(cols):
newArray.data[r][c] = self.data[r][c] + other
return newArray
else:
return NotImplemented # es un tipo de error particular usado en estos metodos
#Ejercicio 5
##No me salió :(
#Ejercicio 6
def __mul__(self, other):
if isinstance(other, Array):
#Validar las dimensiones
if self.shape[1] != other.shape[0]:
raise Exception("Las matrices no son compatibles!")
#Obtener las dimensiones
num_rowsA = self.shape[0]
num_rowsB = other.shape[0]
num_colsB = other.shape[1]
#Crear matriz receptora
newArray = Array([[0 for col in range(num_colsB)] for row in range(num_rowsA)])
#Multiplicar
for i in range(num_rowsA):
for j in range(num_colsB):
for k in range(num_rowsB):
newArray[i,j] = newArray[i,j] + self.data[i][k] * other.data[k][j]
return newArray
#Matriz, entero
elif isinstance(other, (int, float, complex)):
#Obtener las dimensiones
rows, cols = self.shape
#Crear matriz receptora
newArray = Array([[0 for col in range(cols)] for row in range(rows)])
#Multiplicar
for row in range(rows):
for col in range(cols):
newArray.data[row][col] = self.data[row][col] * other
return newArray
else:
return NotImplemented
def __rmul__(self, other):
if isinstance(other, (int, float, complex)):
rows, cols = self.shape
newArray = Array([[0 for col in range(cols)] for row in range(rows)])
for row in range(rows):
for col in range(cols):
newArray.data[row][col] = self.data[row][col] * other
return newArray
else:
return NotImplemented
In [127]:
X = Array([[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15]])
In [128]:
X
Out[128]:
In [129]:
print(X)
In [130]:
X[0,2]
Out[130]:
In [131]:
X
Out[131]:
In [132]:
X[0,0] = 10
In [133]:
X
Out[133]:
In [134]:
Array.zeros(5,5)
Out[134]:
In [135]:
Array.eye(4)
Out[135]:
In [136]:
X.transpose()
Out[136]:
In [137]:
B = Array.eye(5)
In [138]:
B
Out[138]:
In [139]:
X*B
Out[139]: