La lección sobre sistemas lineales presentó la eliminación gaussiana como el procedimiento que np.linalg.solve ejecuta internamente. Esta se ocupa de lo que ese procedimiento deja tras de sí. La eliminación no solo produce una solución: produce una factorización de la matriz, y esa factorización es reutilizable.
Lo que la eliminación produce
Aplicar eliminación gaussiana con pivoteo parcial a equivale a construir tres matrices:
donde es una permutación que registra los intercambios de fila, es triangular inferior con unos en la diagonal y contiene los multiplicadores empleados, y es triangular superior y es el resultado visible de la eliminación.
En la figura, se va formando por eliminación mientras almacena cada multiplicador en la posición que acaba de anularse:
P A = L U
Paso 1 / 7
Nada se descarta. Cada operación de fila queda registrada: los intercambios en , los multiplicadores en , y el resultado en . Esa es la diferencia entre ejecutar la eliminación y factorizar.
from scipy.linalg import lu
P, L, U = lu(A)
np.allclose(P @ L @ U, A) # True
La convención de SciPy difiere de la habitual. Devuelve tal que , mientras que los textos, y el enunciado de más arriba, escriben . Ambas describen la misma factorización: como es una permutación, , de modo que equivale a . La de SciPy es la traspuesta de la del enunciado.
Un subproducto inmediato: el determinante se lee en la diagonal de .
donde es el número de intercambios de fila. Para la matriz de la figura, dos intercambios y una diagonal dan . Es así como np.linalg.det obtiene su resultado: no desarrolla por menores, que costaría , sino que factoriza.
Factorizar una vez, resolver muchas
La factorización cuesta aproximadamente operaciones. Resolver con ella, en cambio, son dos sustituciones triangulares —hacia adelante con , hacia atrás con — que cuestan en conjunto.
Esa asimetría es el motivo de conservar los factores. Cuando el mismo se resuelve contra varios términos independientes, situación frecuente en la práctica, repetir la eliminación desperdicia todo el trabajo caro:
from scipy.linalg import lu_factor, lu_solve
lu_piv = lu_factor(A) # una vez: O(n³)
x1 = lu_solve(lu_piv, b1) # cada uno: O(n²)
x2 = lu_solve(lu_piv, b2)
factor de mejora ×18.0
Recuento de operaciones: 2n³/3 para factorizar, 2n² por cada sustitución triangular.
matriz 500×500 · términos independientes k = 20
La ventaja crece con y con el número de sistemas. Para grande el coste se aproxima al de una sola factorización, con independencia de cuántos términos independientes haya que procesar.
Si todos los términos independientes se conocen de antemano, np.linalg.solve acepta una matriz como segundo argumento y factoriza una sola vez:
B = np.column_stack([b1, b2, b3])
X = np.linalg.solve(A, B) # una factorización, tres soluciones
Cuando la matriz es simétrica: Cholesky
La lección sobre la inversa y la transpuesta estableció que es simétrica y semidefinida positiva. Para matrices con esa estructura existe una factorización más barata:
con triangular inferior de diagonal positiva. Es la factorización de Cholesky, y requiere que sea simétrica y definida positiva. Cuesta operaciones, la mitad que LU, porque explota la simetría en lugar de ignorarla.
L = np.linalg.cholesky(K) # falla si K no es definida positiva
alpha = np.linalg.solve(L.T, np.linalg.solve(L, y))
El fallo es informativo, no un inconveniente: si Cholesky no converge, la matriz no es definida positiva, lo que en un proceso gaussiano suele indicar una matriz de covarianza mal condicionada. La práctica habitual consiste en añadir un término de regularización a la diagonal, , que desplaza todos los valores propios y restaura la definición positiva.
Cuando el sistema es rectangular: QR
Para mínimos cuadrados, la lección anterior señaló que formar eleva al cuadrado el número de condición. La alternativa es factorizar directamente:
con de columnas ortonormales y triangular superior. Sustituyendo en las ecuaciones normales, el problema se reduce a , un sistema triangular, sin llegar a construir en ningún momento.
Q, R = np.linalg.qr(A)
w = np.linalg.solve(R, Q.T @ b)
Es, en esencia, lo que hace np.linalg.lstsq internamente.
| Factorización | Requiere | Coste | Se usa para |
|---|---|---|---|
| LU | cuadrada, no singular | sistemas generales, determinante | |
| Cholesky | simétrica definida positiva | covarianzas, procesos gaussianos | |
| QR | rango columna completo | mínimos cuadrados | |
| SVD | ninguna | rango, núcleo, pseudoinversa |
La forma escalonada reducida
La eliminación puede llevarse más allá de hasta la forma escalonada reducida por filas, con pivotes iguales a uno y ceros también por encima de ellos. NumPy no la proporciona, y la ausencia es deliberada: para resolver un sistema no aporta nada que no dé ya, y en coma flotante la decisión de qué entrada es un cero exacto resulta ambigua.
Donde sí tiene lugar es en el álgebra exacta, con sympy:
import sympy as sp
M = sp.Matrix([[2, 1, -1], [-3, -1, 2], [-2, 1, 2]])
M.rref() # aritmética racional exacta, sin redondeo
La distinción es de dominio, no de calidad. sympy opera sobre números racionales exactos y resulta apropiado para determinar estructura —rango, base del núcleo, dependencias entre filas— en matrices pequeñas. numpy y scipy operan en coma flotante sobre implementaciones optimizadas y son la herramienta para el cálculo numérico. Emplear el segundo para razonar sobre estructura exacta, o el primero para resolver un sistema de tamaño considerable, invierte ambos usos.
Por qué no se programa a mano
Las rutinas mostradas delegan en LAPACK, una biblioteca con décadas de depuración cuyas implementaciones tienen en cuenta la jerarquía de memoria: operan por bloques para aprovechar la caché, cosa que una traducción directa del pseudocódigo no hace.
Una implementación propia de la eliminación es un ejercicio instructivo y una mala decisión en producción. Será entre uno y dos órdenes de magnitud más lenta y, con mayor probabilidad, menos estable: el pivoteo, el criterio de detección de singularidad y el tratamiento de los casos límite concentran la dificultad, y son precisamente las partes que un primer intento omite.
Ejercicio. Factorizar con scipy.linalg.lu y comprobar que el producto de la diagonal de U, con el signo correspondiente al número de intercambios, reproduce np.linalg.det(A). Determinar después cuántos términos independientes hacen falta para que factorizar una vez resulte más barato que llamar a solve repetidamente, con .