<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Tarea 1
Step1:  Ejercicio1
Escribe los metodos repr y str para la clase Array de forma que se imprima legiblemente como en numpy arrays.
Step2:  Ejercicio2
Escribe el metodo setitem para que el codigo A[i,j] = new_value cambie el valor de la entrada (i,j) del array.
Step3:  Ejercicio3
Implementa una funcion de zeros para crear arrays "vacios"
Step4:  Implementa una funcion eye(n) que crea la matriz identidad de nxn
Step5:  Ejercicio4
Implementa la funcion de transposicion
Step6:  Ejercicio5
Investiga como implementar el metodo de clase radd
Step7:  Implementa el metodo de clase sub similar a la suma para poder calcular expresiones como A - B para A y B arrays o numeros.
Step8:  Ejercicio6
Implementa las funciones mul y rmul para hacer multiplicacion matricial (y por un escalar).
Step10:  Ejercicio7
Implementa una funcion forward_subs que resuelva sistemas de ecuaciones de la forma $Lx = y$ con $L$ triangular inferior y $y$ cualquier Vector o Array de una columna.
Step12:  Ejercicio8
Implementa una funcion backward_subs que resuelva sistemas $Ux = y$ con $U$ triangular superior y y Vector o Array de una columna.
Step14:  Ejercicio9
Implementa una funcion LU que reciba un Array  $A$ y devuelva 3 arrays $L$,$U$ y $P$ tales que  $PA = LU$ con $L$ trangular inferior, $U$ triangular superior y $P$ matriz de permutacion.
Step15:  Ejercicio10
Implementa una funcion lu_linsolve que resuelva cualquier sistema de ecuaciones Ax = y con A un Array y y un Vector o Array de una columna.
<ASSISTANT_TASK:>
Python Code:
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")
     
        for a in range(nrow):
            for b in range(len(list_of_rows[a])):
                if not type(list_of_rows[a][b]) == int and not type(list_of_rows[a][b]) == float:
                    raise Exception("Hay datos no numericos")
    
    
    
    def __repr__(self): #Ejercicio1
        "Funcion que se manda llamar cuando solo pones el dato en terminal >> dato"
        return self.printdata(self.data)
    
    def printdata(self, data):   #Ejercicio1
        "Funcion que da formato a la impresion de matrices y vectores"
        rows = len(data)
        cadena = "["
        for a in range(rows):
            if a>0: 
                cadena+=str("\n ")                            
            cadena+=str(data[a])
            
        cadena+="]"
        return cadena
    
    def __str__(self): #Ejercicio1
        "Funcion que se manda llamar cuando solo pones llamas a imprimir el dato en terminal >>print(dato)"
        return self.printdata(self.data)    
    
    def __getitem__(self, idx):   #Ejercicio2
        "Metodo para poder obtener datos del objeto mediante un indice"
        return self.data[idx[0]][idx[1]]
    
    def __setitem__(self, idx, new_value):   #Ejercicio2
        "Metodo por el cual podemos asignar valores al objeto mediante indices"
        self.data[idx[0]][idx[1]] = new_value        
    
    def transpose(self):   #Ejercicio4
        "Metodo para obtener la matriz transpuesta del objeto Array"
        rows = self.shape[0]
        cols = self.shape[1]
        transpuesta = [[self.data[x][y] for x in range(rows)] for y in range(cols)]
        return Array(transpuesta)
        #return self.printdata(transpuesta)
    
    
    def __add__(self, other):   #Ejercicio5
        "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(other, (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:
            raise Exception("Data must be either Array class or scalar") # es un tipo de error particular usado en estos metodos
        
    __radd__ = __add__     #Ejercicio5
    
    def __sub__(self, other):   #Ejercicio5
        "Hora de restar"
        "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(other, (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:
            raise Exception("Data must be either Array class or scalar") # es un tipo de error particular usado en estos metodos
    __rsub__ = __sub__     #Ejercicio5
    def __mul__(self,other):   #Ejercicio6
        "Metodo para multiplicar una matriz ya sea con un escalar, u otra matriz(vectores tambien ya que son matrices de 1xn o nx1"
        #if len(other[0])
        if isinstance(other, Array):
            compatible = False #
            if self.shape[1] == other.shape[0]:
                compatible = True            
            else:
                print("Incompatible matrices")
                        
            if compatible:
                rows, cols = self.shape
                matmult = []
                for a in range(rows):                
                    vec = []
                    for b in range(other.shape[1]):                  
                        vec.append(self.multAdd(self.data[a],[other.data[i][b] for i in range(other.shape[0])])) 
                    matmult.append(vec)
                
                return self.printdata(matmult)
            
        elif isinstance(other, (int, float)):            
            rows, cols = self.shape
            newArray = Array([[0. for c in range(cols)] for r in range(rows)])
            newArray.data = [[self.data[y][x]*other for x in range(cols)] for y in range(rows)]
            return newArray
    
    def multAdd(self,parama, paramb):  #Ejercicio6 (auxiliar)
        "Metodo para calcular producto punto"
        sum = 0
        for x in range(len(parama)):
            sum+=(parama[x]*paramb[x])
        return sum
        
    
    __rmul__ = __mul__    #Ejercicio6
Explanation: Tarea 1:
Alumno: Arturo Gonzalez Bencomo
Clase Array completa
La clase array completa se implemento en los ejercicios 1-6 con comentarios en el codigo indicando que metodo se implemento en que ejercicio, posteriormente se comprueba cada seccion correspondiente.
Los ejercicios 7-10 no implicaron modificacion de clase array sino que se agregaron funciones nuevas externas a la clase
End of explanation
dato = Array([[1,2,3],[4,5,6],[8,9,10]])
print(dato)
dato
Explanation: Ejercicio1
Escribe los metodos repr y str para la clase Array de forma que se imprima legiblemente como en numpy arrays.
End of explanation
dato = Array([[1,2,3],[4,5,6],[8,9,10]])
print("Original: ")
print(dato)
print("Acceder a elemento por indices dato[1,2]")
print(dato[1,2])
dato[0,0] = 10
print("Asignamos elemento por indices dato[0,0] = 10")
print(dato)
Explanation: Ejercicio2
Escribe el metodo setitem para que el codigo A[i,j] = new_value cambie el valor de la entrada (i,j) del array.
End of explanation
def zeros(filas, columnas):
    "Clase que crea un objeto de clase Array inicializado con valores en ceros sintaxis matriz_ceros(2,3)"
    return Array([[0 for x in range(columnas)] for y in range(filas)])
zeros(5,5)
Explanation: Ejercicio3
Implementa una funcion de zeros para crear arrays "vacios"
End of explanation
def evaluate(a,b):
    "Evalua si son iguales y regresa 1, si no 0"
    if a == b:
        return 1
    else:
        return 0
def eye(n):
    "Crea matriz diagonal de dim = n x n"
    return Array([[evaluate(x,y) for x in range(n)] for y in range(n)])
eye(5)
Explanation: Implementa una funcion eye(n) que crea la matriz identidad de nxn
End of explanation
dato = Array([[1,2,3,6],[4,5,6,11],[8,9,10,49],[9,12,67,1]])
print("Original: ")
print(dato)
print("Transpuesta")
print(dato.transpose())
dato1 = Array([[1,2,3,6],[4,5,6,11],[8,9,10,49],[9,12,67,1]]).transpose()
print("Transpuesta: ")
print(dato1)
Explanation: Ejercicio4
Implementa la funcion de transposicion
End of explanation
dato = Array([[1,2,3],[4,5,6],[8,9,10]])
dato1 = Array([[1,2,71],[14,15,16],[28,29,20]])    
print("elemento1" )
print(dato)
print("elemento2" )
print(dato1)
print("suma de ambos")
print(dato+dato1)
print("suma de Array y escalar")
print(dato+1)
print("suma de escalar y Array")
print(1+dato)
Explanation: Ejercicio5
Investiga como implementar el metodo de clase radd
End of explanation
print("Resta de ambos")
print(dato-dato1)
print("Resta de Array y escalar")
print(dato-1)
print("Resta de escalar y Array")
print(1-dato)
    
Explanation: Implementa el metodo de clase sub similar a la suma para poder calcular expresiones como A - B para A y B arrays o numeros.
End of explanation
dato = Array([[1,2,3],[4,5,6],[1,2,3],[4,5,6]])
dato1 = Array([[1,2,8,78],[14,15,12,21],[28,29,78,90]])    
print("primer elemento")
print(dato)
print("Segundo elemento")
print(dato1)
print ("multiplicacion")
print(dato*dato1)
print("multiplicacion de primer elemento por escalar 5")
print(dato * 5)
print("multiplicacion de escalar 5 por primer elemento")
print(5*dato)
Explanation: Ejercicio6
Implementa las funciones mul y rmul para hacer multiplicacion matricial (y por un escalar).
End of explanation
def forward_subs(matrix,vector):
    "Resuelve matrices de la forma Lx = b"
    
     Codigo de validacion, tamanios, matriz L
    flag = False    
    y = None
    if type(matrix) == Array and type(vector) == Array:        
        if matrix.shape[0] == vector.shape[0]:
           if matrix.shape[0] == matrix.shape[1]: 
               for y in range(matrix.shape[0]):
                   for x in range(matrix.shape[1]):
                       if x>y:
                           if not matrix[y,x] == 0:
                               raise Exception("No es matriz L")
                           else:
                               flag = True
           else:
                print("No es matriz cuadrada")
        else:
            print("No concuerdan las dimensiones de la matriz y del vector")
    
    if flag:
        print("Es matriz L")
        y = []
        for a in range(vector.shape[0]):
            y.append(vector[a,0])
            for b in range(a):
                y[a]-=matrix[a,b]*y[b]                    
            y[a] /= matrix[a,a]
                
    return y
dato = Array([[1,0,0,0],[4,2,0,0],[1,2,1,0],[3,4,5,2]])    
print("Matriz: ")
print(dato)
print("vector")
vector = Array([[1],[2],[3],[6]])
print(vector)
print("solucion de resolucion de Lx = b")
print(forward_subs(dato,vector))
Explanation: Ejercicio7
Implementa una funcion forward_subs que resuelva sistemas de ecuaciones de la forma $Lx = y$ con $L$ triangular inferior y $y$ cualquier Vector o Array de una columna.
End of explanation
def backward_subs(matrix, vector):
    "Resuelve matrices de la forma Lx = b"
     Codigo de validacion, tamanios, matriz L
    flag = False    
    y = None
    if type(matrix) == Array and type(vector) == Array:        
        if matrix.shape[0] == vector.shape[0]:
           if matrix.shape[0] == matrix.shape[1]: 
               for y in range(matrix.shape[0]):
                   for x in range(matrix.shape[1]):
                       if x<y:
                           if not matrix[y,x] == 0:
                               raise Exception("No es matriz U")
                           else:
                               flag = True
           else:
                print("No es matriz cuadrada")
        else:
            print("No concuerdan las dimensiones de la matriz y del vector")
    
    if flag:
        print("Es matriz U")
        y = [0] * vector.shape[0]
        for a in range(vector.shape[0]-1,-1,-1):
            y[a] = vector[a,0]
            for b in range(a+1,vector.shape[0]):
                y[a]-=matrix[a,b]*y[b]                    
            y[a] /= matrix[a,a]                
            
    return y
dato = Array([[1,1],[0,2]])
print("Matriz: ")
print(dato)
print("vector")
vector = Array([[12],[20]])
print(vector)
print("solucion de resolucion de Ux = b")
print(backward_subs(dato,vector))
Explanation: Ejercicio8
Implementa una funcion backward_subs que resuelva sistemas $Ux = y$ con $U$ triangular superior y y Vector o Array de una columna.
End of explanation
def LU(A):    
    "Funcion para realizar descomposicion de matriz en PA = LU"
    if type(A == Array):
        
        L = [[0 for x in range(A.shape[1])] for y in range(A.shape[0])]        
        for a in range(A.shape[0]):
            L[a][a] = 1
         
        L = Array(L)
        
        Matriz de permutacion
        list_tuples = [(0,0) for x in range(A.shape[0])]
                    
        for a in range(A.shape[0]):            
            list_tuples[a] = (a,A.data[a])
        
        data = []
        data.append((0,A.data[0]))
        del list_tuples[0]            
        
        if len(list_tuples) == 1:
            starting = 0
        else:
            starting = 1
            
        for row in range(starting,len(list_tuples)):     
            temp = sorted(list_tuples, key=lambda value: value[1][row], reverse=True)
            data += temp
                                        
        U = []
        for row in data:
            U.append(row[1])
        
        U = Array(U)
            
        permutation_matrix = [[0 for x in range(A.shape[0])] for y in range(A.shape[0])]        
        permutation_matrix = Array(permutation_matrix)                
        
        for row in range(len(data)):    
            permutation_matrix[row, data[row][0]] = 1   
                       
        n = A.shape[0]
        for a in range(n):
            for b in range(a+1,n):    
                multiplicador = float((U[b,a]/U[a,a]))
                L[b,a] = multiplicador
                multiplicador= multiplicador*(-1)
                for c in range(n):						
                    U[b,c] += U[a,c]*multiplicador
        return [L,U,permutation_matrix]
A = Array([[1,1,-1],[1,-2,3],[2,3,1]])
#A = Array([[1,7,-1],[1,6,8],[2,3,7]])
print("Matriz original")
print(A)    
L,U,P = LU(A)
print("Matrices de Lower, Upper y Permutacion son: ")
print("L")
print(L)
print("U")
print(U)
print("P")
print(P)
Explanation: Ejercicio9
Implementa una funcion LU que reciba un Array  $A$ y devuelva 3 arrays $L$,$U$ y $P$ tales que  $PA = LU$ con $L$ trangular inferior, $U$ triangular superior y $P$ matriz de permutacion.
End of explanation
def lu_linsolve(A,y):
    "Funcion para resolver sistemas de ecuaciones basado en la descomposicion LU"
    solucion = None
    if type(A) == Array and type(y) == Array:
        if A.shape[0] == y.shape[0]:
            L,U,P = LU(A)
            new_vector = []
            for x in range(P.shape[0]):
                for z in range(P.shape[1]):
                    if(P[x,z] == 1):
                        new_vector.append([y[z,0]])
                        
            new_vector = Array(new_vector)                        
            x = Array([forward_subs(L,new_vector)])
            solucion = backward_subs(U, x.transpose())
                        
        else:
            print("No son compatibles Array y vector")
            print("Forma de matriz ")
            print(A.shape)
            print("Forma de vector")
            print(y.shape)
    else:
        print("No son estructuras de datos Array")
        
    return solucion
matriz = Array([[1,1,3],[2,4,4],[2,2,2]])
print("La matriz es: ")
print(matriz)
vector = Array([[30],[68],[36]])    
print("El vector es: ")
print(vector)
solucion = lu_linsolve(matriz,vector)
print("La solucion al sistema de ecuaciones es")
print(solucion)
Explanation: Ejercicio10
Implementa una funcion lu_linsolve que resuelva cualquier sistema de ecuaciones Ax = y con A un Array y y un Vector o Array de una columna.
End of explanation
<END_TASK>