Creando una sistema de Algebra Lineal


In [1]:
# import numpy as np    # Importo librería NumPy como np

 Creo clase con métodos para realizar operaciones de álgebra lineal con vectores y matrices


In [2]:
import copy as cp

class Array:    
    #----------[ Metodo constructor y validador:
    def __init__(self, list_of_rows): 
        """ Metodo Constructor y Validador
        2. Un validador
        
        Parámetros:
        .- list_of_rows: lista de arrays que asigna al atributo DATA 
        y ademas en SHAPE define la dimensión del mismo.
        
        Ejemplo: 
            A = Array([[1,2,3],[4,5,6],[7,8,9]])
        """
        # 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")
    #---------- Metodo constructor y validador ].
    
    
    #----------[ Metodo para imprimir sin función Print:
    def __repr__(self):
        """ 1. Un metodo para imprimir mejor...
        Metodo para imprimir un Array sin utilizar print
        """
        nrow, ncol = self.shape
        data_out  = "array(["
        data_line = "["
          
        espacio = max(max([[len(str(self.data[i][j])) for i in range(nrow)] for j in range(ncol)]))                                         
        
        for i in range(nrow):
            data_line  = "[" + str(", ".join(map(lambda i: ('{:.0f}'.format(i)).rjust(espacio), self.data[i]))) + "]"
            if i < (nrow - 1):
                data_out += data_line + ",\n       "
            else: 
                data_out += data_line + "])"
                                
        return (data_out)
    #---------- Metodo para imprimir sin función Print ].
 

    #----------[ Metodo para imprimir con función Print: 
    def __str__(self):
        """ 1. Un metodo para imprimir mejor...
        Metodo para imprimir un Array utilizando print
        
        """      
        nrow, ncol = self.shape
        data_out  = "["
        data_line = "["
        
        espacio = max(max([[len(str(self.data[i][j])) for i in range(nrow)] for j in range(ncol)]))                                  
        
        for i in range(nrow):
            data_line  = "[" + str(" ".join(map(lambda i: ('{:.0f}'.format(i)).rjust(espacio), self.data[i]))) + "]"
           
            if i < (nrow - 1):
                data_out += data_line + "\n "
            else: 
                data_out += data_line + "]"
                                
        return (data_out)
    #---------- Metodo para imprimir con función Print ].
    
    
    #----------[ Metodo para obtener un item: 
    def __getitem__(self, idx, ini=0):
        """ 3.1. Indexing and Item assignment
        Retorna un item
        
        """  
        if ("slice" not in str(idx[0])) & ("slice" not in str(idx[1])) :
            nrow, ncol = self.shape
            if idx[0] <= nrow | idx[1] <= ncol:
                if ini == 0:
                    return self.data[idx[0]][idx[1]]
                else:
                    return self.data[(idx[0] - 1)][(idx[1] -1) ]
            else:
                raise Exception("La dimensión no corresponde con la del Array!")
        else:
            return NotImplemented 
    #---------- Metodo para obtener un item ].
    
    
    #----------[ Metodo para modificar un item:
    def __setitem__(self, idx, new_value, ini=0):
        """ 3.2. Indexing and Item assignment
        Modifica el valor de un item particular:        
        """
        nrow, ncol = self.shape
        if idx[0] <= nrow | idx[1] <= ncol:
            if ini == 0:
                self.data[idx[0]][idx[1]] = new_value
            else:
                self.data[(idx[0] - 1)][(idx[1] -1)] = new_value
            print("Item modificado.")
            
        else:
            raise Exception("La dimensión no corresponde con la del Array!")
    
    #---------- Metodo para modificar un item ].
    
    
    #----------[ Función para crear Matriz de Ceros:
    def zeros(shape): 
        """ 4.1. Iniciar una matriz en Ceros
        Genera un Array de Ceros de la dimensión indicada
        
        Parametros
        ----------
        shape: (n,m) -> n filas x m columnas
        """        
        if isinstance(shape, tuple):
            nrow, ncol = shape
        elif isinstance(shape, int):
            nrow = ncol = shape        
        else:
            raise Exception("Parámetro no definido.")
        
        newArray = Array([[0 for i in range(ncol)] for j in range(nrow)])      
        return newArray
    #---------- Función para crear Matriz de Ceros ]. 
    

    #----------[ Función para crear Matriz Identidad:
    def eye(n,m=0,k=0):
        """ 4.2. Iniciar una matriz con Unos en la diagonal
        
        Parametros
        ----------
        n: Número de filas
        m: Número de columnas (opcional)
           Por defecto m = n
        k: Desplaza la diagonal (opcional)
           k = 0: Daigonal principal (valor por defect)
           k > 0: Diagonal superior
           k < 0: Diagonal inferior
        """
        if m == 0: m = idxy = n
        elif n > m: idxy = n
        else: idxy = m
            
        idx = 0
        idy = 0
            
        newArray = Array.zeros((n,m))            
        for i in range(idxy):
            idx = idy = i            
            if k == 0:                  
                if (idx < n) & (idy < m):
                    # Asigno 1 a la diagonal
                    newArray.data[idx][idy] = 1.0  
            elif k > 0:
                idy += k 
                if (idx < n) & (idy < m):
                    # Asigno 1 a la diagonal
                    newArray.data[idx][idy] = 1.0  
            else:                        
                idx += k                 
                if (idx < n) & (idy < m) & (idx >= 0):
                    # Asigno 1 a la diagonal
                    newArray.data[idx][idy] = 1.0  
                
        return newArray
    #---------- Función para crear Matriz Identidad ].
    
    
    #----------[ Metodo para generar la matriz transpuesta:
    def transpose(self):
        """ 5. Transposicion
        Calcula la Transpuesta de una matriz
        """
        nrow, ncol = self.shape
        return Array([[self.data[j][i] for j in range(nrow)] for i in range(ncol)])
    #---------- Metodo para generar la matriz transpuesta ].
    
    
    #----------[ Metodo para sumar arrays:
    def __add__(self, other):
        """ 6.1. Suma
        Suma Arrays
        """
        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
    #---------- Metodo para sumar arrays ].


    #----------[ Metodo para sumar arrays:
    def __radd__(self, other):
        """ 6.2. Suma
        Suma Arrays
        """
        return self.__add__(other)
    #---------- Metodo para sumar arrays ].
    
    
    #----------[ Metodo para restar arrays:
    def __sub__(self, other):
        """  6.3. Resta 
        Resta Arrays
        """         
        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
    #---------- Metodo para restar arrays ].
    
    
    #----------[ Metodo para sumar arrays:
    def __rsub__(self, other):
        """ 6.3. Resta 
        Resta Arrays
        """        
        return self.__sub__(other)
    #---------- Metodo para sumar arrays ].
    
    
    
    #----------[ Metodo para Multiplicar arrays:
    def __mul__(self, other):
        """ 7. Multiplicacion Matricial
        Multiplica Arrays:
        """
        if isinstance(other, Array):
            other_t = other.transpose() # Obtengo transpuestas
            idx = 0
            idy = 0                        
            
            list_of_rows = []
            list_of_calc = [0 for x in range(self.shape[0])]  # Creo lista de long. n con Ceros
                        
            for x in range(self.shape[0] * other.shape[1]): # Itero n x m                
                # Calculo la suma de multiplicar Fila Nx por Columna My               
                # list_of_calc[idx] = (sum(i*j for i,j in zip(self.data[idy], other_t.data[idx])))
                list_of_calc[idx] = (sum(i*j for i,j in zip(other_t.data[idy], self.data[idx])))

                if idx == (self.shape[0] - 1):
                    idx = 0
                    idy += 1
                    list_of_rows.append(cp.copy(list_of_calc)) 
                else: 
                    idx += 1 
            
            if "Vector" not in str(type(other)):
                return Array(list_of_rows)
            else: 
                return Vector(list_of_calc)
    
        elif isinstance(other, int):            
            if "Vector" not in str(type(self)):                
                nrow, ncol = self.shape
                return Array([[self.data[i][j] * other for j in range(nrow)] for i in range(ncol)])
                
            else:
                return NotImplemented
        else:
            return NotImplemented
    #---------- Metodo para Multiplicar arrays: ].
    
    
    #----------[ Metodo para sumar arrays:
    def __rmul__(self, other):
        """ 7. Multiplicacion Matricial
        Multiplica Arrays:
        """
        return self.__mul__(other)
    #---------- Metodo para sumar arrays ].
    
#-------------------------------------------------------------------------------------------------------------#
class Vector(Array): # declara que Vector es un tipo de Array
    def __init__(self, list_of_numbers):
        self.vdata = list_of_numbers
        list_of_rows = [[x] for x in list_of_numbers]
        return Array.__init__(self, list_of_rows)
    
    def __repr__(self):
        return "Vector(" + str(self.vdata) + ")"
    
    def __str__(self):
        return str(self.vdata)
    
    def __add__(self, other):
        new_arr = Array.__add__(self, other)
        return Vector([x[0] for x in new_arr.data])
    
    def __sub__(self, other):        
        new_arr = Array.__sub__(self, other)
        return Vector([x[0] for x in new_arr.data])
    
    #----------[ Metodo para Multiplicar arrays:
    def __mul__(self, other):
        """ 8. Multiplicacion de Vectores
        Multiplica Arrays:
        """    
        print(NotImplemented)
        
        return NotImplemented
    #---------- Metodo para Multiplicar arrays: ].
#-------------------------------------------------------------------------------------------------------------#

Uso de clase Array

Generar array:


In [3]:
A = Array([[1,2,3], [4,5,6],[7,8,9]])
A.__dict__     # el campo escondido __dict__ permite acceder a las propiedades de clase de un objeto


Out[3]:
{'data': [[1, 2, 3], [4, 5, 6], [7, 8, 9]], 'shape': (3, 3)}

In [4]:
A.data, A.shape


Out[4]:
([[1, 2, 3], [4, 5, 6], [7, 8, 9]], (3, 3))

Visualizar datos:


In [5]:
print(A)  # Muestro el contenido de A utilizando la función print


[[1 2 3]
 [4 5 6]
 [7 8 9]]

In [6]:
A        # Muestro el contenido de A sin utilizar la función print


Out[6]:
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

Accediendo a una posición:


In [7]:
A[0,0]       # Accedo al valor de una posición


Out[7]:
1

In [8]:
A[0,0] = -3  # Modifico el valor de una posición
A


Item modificado.
Out[8]:
array([[-3,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9]])

Zeros


In [9]:
Z = Array.zeros((3,3))  # Genero matriz de m x n de Ceros
Z


Out[9]:
array([[0, 0, 0],
       [0, 0, 0],
       [0, 0, 0]])

Matriz Identidad


In [10]:
E = Array.eye(3,3)     # Genero matriz de m x n con 1s en la diagonal
E


Out[10]:
array([[  1,   0,   0],
       [  0,   1,   0],
       [  0,   0,   1]])

Transpuesta


In [11]:
T = A.transpose()      # Obtengo la transpuesta de A
T


Out[11]:
array([[-3,  4,  7],
       [ 2,  5,  8],
       [ 3,  6,  9]])

Operaciones entre Arrays

Suma de Matrices


In [12]:
S1 = A + T          # Sumo Matrices
S1


Out[12]:
array([[-6,  6, 10],
       [ 6, 10, 14],
       [10, 14, 18]])

Suma de Matriz + Escalar


In [13]:
S2 = A + 10        # Sumo Matriz + Escalar
S2


Out[13]:
array([[ 7, 12, 13],
       [14, 15, 16],
       [17, 18, 19]])

Multiplicación de matrices


In [14]:
M1 = A * T             # Multiplico Matrices
M1


Out[14]:
array([[ 22,  16,  22],
       [ 16,  77, 122],
       [ 22, 122, 194]])

Multiplicación de Matriz x Escalar


In [15]:
M2 = A * 5            # Multiplico Matriz por un Escalar
M2


Out[15]:
array([[-15,  10,  15],
       [ 20,  25,  30],
       [ 35,  40,  45]])

Vectores


In [16]:
Vector([1,2,3]).__dict__


Out[16]:
{'data': [[1], [2], [3]], 'shape': (3, 1), 'vdata': [1, 2, 3]}

In [17]:
vec = Vector([1,2,3])
vec


Out[17]:
Vector([1, 2, 3])

Suma de Vectores


In [18]:
Vector([1,2,3]) + Vector([5,-2,0])


Out[18]:
Vector([6, 0, 3])

Suma de Vector + Escalar


In [19]:
(vec + 10) - 5


Out[19]:
Vector([6, 7, 8])

Matriz & Vector


In [20]:
MV = A * vec
MV


Out[20]:
Vector([10, 32, 50])