Algoritmo de Matrices Triangulares en Python
Una guía completa que cubre la teoría, la implementación práctica en Python y casos de uso del mundo real.
1. Introducción
Las matrices triangulares son un pilar en álgebra lineal y aparecen en numerosos algoritmos de solución de sistemas lineales, factorización de matrices y optimización numérica. Este artículo explica qué son, cómo identificarlas y generarlas mediante un algoritmo sencillo y muestra ejemplos en Python usando NumPy y SciPy.
2. ¿Qué es una Matriz Triangular?
Una matriz cuadrada A ∈ ℝⁿˣⁿ se denomina:
- Triangular superior si todos los elementos por debajo de la diagonal principal son cero (
a_{ij}=0 ∀ i>j). - Triangular inferior si todos los elementos por encima de la diagonal son cero (
a_{ij}=0 ∀ i).
Ejemplo de matriz triangular superior 3×3:
[[ 2, -1, 0],
[ 0, 3, 5],
[ 0, 0, 4]]
Ejemplo de matriz triangular inferior 3×3:
[[ 7, 0, 0],
[ 2, 5, 0],
[ 1, -3, 6]]
3. Propiedades Clave
- El determinante es el producto de los elementos de la diagonal principal.
- La inversa (si existe) también es triangular del mismo tipo.
- El coste de resolver
Ax=bmediante sustitución directa es O(n²), mucho menor que O(n³) de una eliminación gaussiana completa.
4. Algoritmo para Detectar y Generar Matrices Triangulares
El algoritmo se basa en inspeccionar los índices de la matriz y forzar a cero los elementos que no pertenecen al tipo deseado.
4.1 Pseudocódigo
function triangular(matrix, tipo):
n = matrix.shape[0]
for i in range(n):
for j in range(n):
if tipo == 'superior' and i > j:
matrix[i][j] = 0
elif tipo == 'inferior' and i < j:
matrix[i][j] = 0
return matrix
4.2 Complejidad
El algoritmo recorre n² elementos, por lo que su complejidad temporal es O(n²) y el espacio adicional es O(1) (modificación in‑place).
5. Implementación en Python
Usaremos numpy para la manipulación de matrices y scipy.linalg para comparar con la factorización LU.
5.1 Función genérica
import numpy as np
def triangular(matrix: np.ndarray, tipo: str = 'superior') -> np.ndarray:
"""Convierte una matriz cuadrada en triangular.
Args:
matrix: Matriz cuadrada (numpy.ndarray).
tipo: 'superior' o 'inferior'.
Returns:
La misma matriz modificada en‑place.
"""
if matrix.shape[0] != matrix.shape[1]:
raise ValueError('La matriz debe ser cuadrada.')
n = matrix.shape[0]
for i in range(n):
for j in range(n):
if tipo == 'superior' and i > j:
matrix[i, j] = 0
elif tipo == 'inferior' and i < j:
matrix[i, j] = 0
return matrix
# Ejemplo de uso
A = np.array([[2, -1, 0], [4, 3, 5], [6, 7, 4]], dtype=float)
print('Original:\n', A)
print('Triangular Superior:\n', triangular(A.copy(), 'superior'))
print('Triangular Inferior:\n', triangular(A.copy(), 'inferior'))
5.2 Resolución de sistemas con sustitución
Una vez que A es triangular, podemos resolver Ax = b en O(n²) usando sustitución hacia adelante (inferior) o hacia atrás (superior).
def forward_substitution(L, b):
n = L.shape[0]
x = np.zeros_like(b, dtype=float)
for i in range(n):
sum_ = np.dot(L[i, :i], x[:i])
x[i] = (b[i] - sum_) / L[i, i]
return x
def backward_substitution(U, b):
n = U.shape[0]
x = np.zeros_like(b, dtype=float)
for i in reversed(range(n)):
sum_ = np.dot(U[i, i+1:], x[i+1:])
x[i] = (b[i] - sum_) / U[i, i]
return x
# Sistema de ejemplo
U = triangular(np.array([[2, -1, 0], [0, 3, 5], [0, 0, 4]], dtype=float), 'superior')
b = np.array([1, 2, 3], dtype=float)
print('Solución (backward):', backward_substitution(U, b))
6. Comparativa con Otras Descomposiciones
Descomposición LU
- Factoriza
A = L·U(L triangular inferior, U triangular superior). - Requiere pivoteo parcial para estabilidad numérica.
- Coste: O(n³) en general.
Factorización de Cholesky
- Aplica solo a matrices simétricas definidas positivas.
- Produce
A = L·Lᵀ(L triangular inferior). - Coste: ~½·O(n³) y mayor estabilidad que LU para matrices SPD.
En contraste, trabajar directamente con una matriz ya triangular elimina la necesidad de factorizar, reduciendo tanto el tiempo de cómputo como el consumo de memoria.
7. Buenas Prácticas y Optimización
- Tipo de dato (dtype): Usa
float64solo cuando la precisión lo justifique;float32reduce la huella de memoria y mejora la caché. - Memoria contigua: Asegúrate de que la matriz sea
C‑contiguous(`matrix.flags['C_CONTIGUOUS']`) para que los bucles de sustitución aprovechen la prefetching. - Vectorización: Cuando sea posible, reemplaza los bucles por operaciones de
numpy.dotoeinsumpara aprovechar BLAS. - Pivoteo y estabilidad: Si la matriz original no es estrictamente triangular, realiza una factorización LU con pivoteo antes de extraer L o U.
8. Compatibilidad y Requisitos
El código funciona con:
- Python ≥ 3.8
- NumPy ≥ 1.20
- SciPy ≥ 1.6 (solo para comparativas)
En entornos sin NumPy, la lógica sigue siendo válida usando listas anidadas, aunque el rendimiento se degrada significativamente.
9. Troubleshooting Común
| Problema | Causa | Solución |
|---|---|---|
| División por cero al resolver | Elemento diagonal = 0 | Aplicar pivoteo o verificar que la matriz sea no singular |
| Resultado inesperado (cifras muy pequeñas) | Precisión insuficiente (float32) | Usar float64 o escalar la matriz |
| Rendimiento pobre en matrices grandes (>10⁴) | Bucle Python puro | Reemplazar por operaciones vectorizadas o usar numba JIT |
10. Caso de Uso Real: Análisis de Redes Eléctricas
En el método de nodos, la matriz de admitancias del sistema es simétrica y positiva definida. Después de aplicar la reducción de componentes, la matriz resultante es típicamente triangular inferior. Resolver Y·V = I mediante sustitución hacia adelante permite calcular rápidamente los voltajes de nodo, incluso para redes con decenas de miles de nodos.
# Simulación simplificada
import numpy as np
# Matriz de admitancias (triangular inferior)
Y = np.tril(np.random.rand(5000, 5000))
I = np.random.rand(5000)
V = forward_substitution(Y, I)
print('Voltajes calculados (primeros 5):', V[:5])
Este enfoque reduce el tiempo de simulación de varios minutos (con LU) a segundos, lo que es crítico para estudios de contingencia en tiempo real.
11. Conclusiones
Las matrices triangulares son una herramienta poderosa cuando se trata de optimizar cálculos lineales. Con un algoritmo O(n²) sencillo y una implementación en Python que aprovecha NumPy, puedes:
- Detectar y forzar la forma triangular.
- Resolver sistemas lineales de forma directa y eficiente.
- Comparar su rendimiento frente a factorizaciones más costosas como LU o Cholesky.
Adoptar estas buenas prácticas mejora la escalabilidad de tus aplicaciones, desde análisis de datos hasta simulaciones de ingeniería.
Algoritmo de Matrices Triangulares en Python: Conceptos, Implementación y Casos de Uso