Algoritmo de Análisis de Rango y Nulidad
El rango y la nulidad (dimensión del núcleo) son dos conceptos fundamentales en álgebra lineal que describen la solución de sistemas lineales y la estructura de una matriz. En este artículo profundizamos en el algoritmo clásico para obtener ambos valores, presentamos una implementación robusta en Python y lo comparamos con técnicas modernas como la descomposición en valores singulares (SVD) y la factorización LU.
1. Fundamentos Teóricos
Para una matriz A ∈ ℝ^{m×n}:
- Rango (rank): número máximo de columnas linealmente independientes; equivale a la dimensión del espacio columna.
- Nulidad (nullity): dimensión del núcleo Ker(A), es decir, el número de soluciones libres del sistema Ax = 0.
El teorema de rango‑nulidad establece que:
rank(A) + nullity(A) = n
2. Algoritmo Clásico (Reducción a Forma Escalonada)
El método más directo consiste en llevar A a su forma escalonada por filas (REF) o forma escalonada reducida por filas (RREF) mediante eliminación de Gauss‑Jordan. Los pivotes determinan el rango; las columnas sin pivote forman la base del núcleo.
Pasos del algoritmo
- Aplicar eliminación de Gauss para obtener REF.
- Contar los pivotes →
rank. - Identificar columnas libres →
nullity = n - rank. - Construir una base del núcleo resolviendo Ax = 0 con variables libres.
Este proceso tiene complejidad O(m·n·min(m,n)), suficiente para matrices de tamaño moderado (< 10⁴ elementos). Para matrices muy grandes o dispersas, se recomiendan técnicas basadas en SVD o factorizaciones iterativas.
3. Implementación en Python
Utilizaremos numpy para operaciones de bajo nivel y scipy.linalg para la reducción a RREF mediante la función lu y sustitución hacia atrás.
import numpy as np
from scipy.linalg import lu
def rref(matrix, tol=1e-12):
"""Devuelve la forma escalonada reducida por filas y la lista de pivotes.
Args:
matrix (np.ndarray): Matriz de entrada (m x n).
tol (float): Tolerancia para considerar un número como cero.
Returns:
tuple: (R, pivots) donde R es la matriz RREF y pivots es una lista de índices de columnas pivote.
"""
A = matrix.astype(float).copy()
m, n = A.shape
pivots = []
row = 0
for col in range(n):
# Busca el pivote máximo en la columna actual
max_row = np.argmax(np.abs(A[row:, col])) + row
if abs(A[max_row, col]) < tol:
continue # columna nula, pasa a la siguiente
# Intercambia filas
A[[row, max_row]] = A[[max_row, row]]
pivots.append(col)
# Normaliza la fila del pivote
A[row] = A[row] / A[row, col]
# Elimina la columna del pivote en otras filas
for r in range(m):
if r != row:
A[r] -= A[r, col] * A[row]
row += 1
if row == m:
break
# Redondea valores próximos a cero
A[np.abs(A) < tol] = 0.0
return A, pivots
def rank_and_nullity(matrix, tol=1e-12):
"""Calcula rango y nulidad usando la RREF.
Returns:
tuple: (rank, nullity, nullspace_basis)
"""
rref_mat, pivots = rref(matrix, tol)
rank = len(pivots)
n = matrix.shape[1]
nullity = n - rank
# Construye una base del núcleo
free_cols = [c for c in range(n) if c not in pivots]
basis = []
for free in free_cols:
vec = np.zeros(n)
vec[free] = 1.0
# Cada pivote se expresa en términos de variables libres
for i, piv in enumerate(pivots):
vec[piv] = -rref_mat[i, free]
basis.append(vec)
nullspace = np.column_stack(basis) if basis else np.empty((n,0))
return rank, nullity, nullspace
# Ejemplo de uso
if __name__ == "__main__":
A = np.array([[1, 2, 3, 4],
[2, 4, 6, 8],
[1, 1, 0, 1]], dtype=float)
r, n, ns = rank_and_nullity(A)
print("Rango:", r)
print("Nulidad:", n)
print("Base del núcleo (columnas):\n", ns)
El código anterior es completamente compatible con Python 3.8+ y solo depende de numpy y scipy, paquetes presentes en la mayoría de entornos científicos.
4. Ejemplos Prácticos
Ejemplo 1: Sistema subdeterminado
A = np.array([[1, 2, 1],
[2, 4, 2]], dtype=float)
# La segunda fila es múltiplo de la primera → rango = 1, nulidad = 2
r, n, ns = rank_and_nullity(A)
print(r, n)
print(ns)
Salida esperada:
1 2 [[ -2. 1.] [ 1. 0.] [ 0. 1.]]
Ejemplo 2: Matriz densa de 5×5
np.random.seed(0)
A = np.random.randint(-5, 6, size=(5,5)).astype(float)
print("Matriz A:\n", A)
rank, nullity, ns = rank_and_nullity(A)
print("Rango:", rank)
print("Nulidad:", nullity)
if nullity:
print("Base del núcleo:\n", ns)
5. Comparativa con Otros Enfoques
Algoritmo clásico (RREF)
- Ventajas: Simple, sin dependencias externas, fácil de entender y depurar.
- Desventajas: Sensible a errores de redondeo en matrices mal condicionadas; complejidad O(m·n·min(m,n)).
- Escalabilidad: Adecuado hasta ~10⁴ elementos; no recomendado para matrices muy grandes (>10⁶).
SVD (Singular Value Decomposition)
- Ventajas: Proporciona rango robusto mediante umbral de valores singulares; maneja matrices mal condicionadas.
- Desventajas: Mayor coste computacional (O(m·n·min(m,n))) y requiere
scipy.linalg.svdonumpy.linalg.svd. - Escalabilidad: Implementaciones optimizadas (LAPACK) permiten matrices de varios millones de elementos con hardware adecuado.
En la práctica, se recomienda usar SVD cuando la precisión numérica es crítica (p.ej., análisis de datos, compresión) y el algoritmo clásico cuando se necesita velocidad y la matriz está bien condicionada.
6. Rendimiento y Optimización
- Vectorización: Evitar bucles Python en la medida de lo posible; la rutina
rrefya está vectorizada en operaciones de fila. - Tipos de datos: Utilizar
float64por defecto; para matrices enormes y esparcidas, cambiar ascipy.sparsey aplicarsvds(SVD esparcida). - Paralelismo:
numpydelega a BLAS/OpenBLAS que aprovecha multihilos automáticamente. - Umbral de tolerancia: Ajustar
tolsegún la precisión de la máquina (p.ej., 1e-10 para doble precisión).
7. Troubleshooting y Buenas Prácticas
Problemas comunes
- Resultado de rango incorrecto: Verifique que la tolerancia no sea demasiado alta; valores muy pequeños pueden considerarse pivotes.
- Memoria insuficiente: Para matrices > 10⁶ elementos, migre a estructuras esparcidas (
scipy.sparse.csr_matrix) y usesvdsen lugar de RREF. - Inestabilidad numérica: Cuando el determinante está cerca de cero, prefiera SVD y analice los valores singulares en vez de los pivotes.
Checklist de implementación
- Confirmar versión de Python (≥3.8) y versiones de
numpy/scipy(≥1.10). - Definir una tolerancia adecuada basada en
np.finfo(float).eps. - Validar la forma de la matriz (2‑D) y tipos de datos.
- Ejecutar pruebas unitarias con matrices de rango completo, parcial y nulo.
- Documentar la función con
docstringy ejemplos de uso (doctest).
8. Seguridad y Compatibilidad
El algoritmo en sí no expone vulnerabilidades, pero al integrar código en aplicaciones web o servicios de API se deben considerar:
- Sanitizar la entrada del usuario para evitar inyección de código (p.ej.,
evalno debe usarse). - Limitar el tamaño de la matriz recibido (p.ej.,
max_n = 5000) para prevenir ataques de denegación de servicio. - Usar entornos virtuales (
venvoconda) para aislar dependencias y garantizar reproducibilidad.
9. Conclusiones
El análisis de rango y nulidad sigue siendo una herramienta esencial para la resolución de sistemas lineales, la detección de dependencia lineal y la compresión de datos. La implementación basada en RREF ofrece claridad didáctica y rapidez para matrices pequeñas a medianas, mientras que técnicas como SVD o factorizaciones esparcidas deben usarse en entornos de gran escala o cuando la precisión numérica es crítica.
Dominar ambas aproximaciones permite a los ingenieros de datos, científicos y desarrolladores seleccionar la estrategia adecuada según el caso de uso, el tamaño de los datos y los requisitos de rendimiento.
Algoritmo de Análisis de Rango y Nulidad: Conceptos, Implementación en Python y Comparativas Avanzadas