Álgebra Lineal Aplicada a las Ecuaciones de Maxwell
Algoritmos, buenas prácticas y ejemplos completos en Python.
1. Introducción
Las ecuaciones de Maxwell describen el comportamiento de los campos eléctricos y magnéticos. En la práctica de simulación electromagnética, la mayoría de los métodos numéricos (FEM, FDTD, MME) convierten el problema continuo en un sistema lineal de la forma Ax = b. Este artículo muestra paso a paso cómo construir y resolver ese sistema usando herramientas de álgebra lineal en Python.
2. Fundamentos de las ecuaciones de Maxwell
- Gauss para el eléctrico: \(\nabla\cdot\mathbf{D}=\rho\)
- Gauss para el magnético: \(\nabla\cdot\mathbf{B}=0\)
- Faraday: \(\nabla\times\mathbf{E}= -\frac{\partial \mathbf{B}}{\partial t}\)
- Ampère‑Maxwell: \(\nabla\times\mathbf{H}= \mathbf{J}+\frac{\partial \mathbf{D}}{\partial t}\)
Al discretizar el dominio (malla de elementos o celda FDTD) cada ecuación se convierte en una relación algebraica entre nodos o celdas, generando matrices esparcidas extremadamente grandes.
3. De la forma diferencial a la forma matricial
En el método de los elementos finitos (FEM) la formulación débil lleva a la siguiente estructura:
\[\mathbf{K}\,\mathbf{e}=\mathbf{f}\]
donde:
- \(\mathbf{K}\) es la matriz de rigidez (combina curl‑curl y grad‑grad).
- \(\mathbf{e}\) representa los valores nodales del campo eléctrico.
- \(\mathbf{f}\) contiene fuentes (corrientes, condiciones de frontera).
En FDTD se obtiene un sistema explícito del tipo Ax_{n+1}=b donde A es una matriz tridiagonal por cada dirección espacial.
4. Algoritmos de solución lineal
Dependiendo del tamaño y la densidad de A, se eligen diferentes métodos:
- Directos (LU, Cholesky): Ideales para matrices < 10⁶ elementos, garantizan solución exacta (hasta error numérico).
- Iterativos (Conjugate Gradient, GMRES): Recomendados para matrices esparcidas >10⁶, requieren pre‑condicionadores.
- Eigenvalue solvers: Cuando se busca modos propios (cavidades resonantes).
Python ofrece scipy.linalg (directo) y scipy.sparse.linalg (iterativo).
5. Implementación en Python
A continuación se muestra una plantilla genérica que puede adaptarse a FEM o FDTD.
import numpy as np
import scipy.sparse as sp
import scipy.sparse.linalg as spla
# --------------------------------------------------
# 1. Construcción de la matriz esparcida K
# --------------------------------------------------
# Ejemplo simple 2‑D usando elementos bilineales en una cuadrícula
N = 100 # número de nodos por dirección
h = 1.0 / (N-1)
size = N * N
# Operador laplaciano 5‑point stencil
main_diag = -4 * np.ones(size)
side_diag = np.ones(size-1)
up_down_diag = np.ones(size-N)
# Corregir los cruces de fila
for i in range(1, N):
side_diag[i*N-1] = 0
K = sp.diags([main_diag, side_diag, side_diag, up_down_diag, up_down_diag],
[0, -1, 1, -N, N], format='csr') / h**2
# --------------------------------------------------
# 2. Fuente (vector b)
# --------------------------------------------------
b = np.zeros(size)
# Fuente puntual en el centro
i_center = N//2 * N + N//2
b[i_center] = 1.0
# --------------------------------------------------
# 3. Solución directa (LU) – para pruebas pequeñas
# --------------------------------------------------
phi_direct = spla.spsolve(K, b)
# --------------------------------------------------
# 4. Solución iterativa (CG) con pre‑condicionador Incomplete LU
# --------------------------------------------------
M = spla.spilu(K) # factorización ILU
M_x = lambda x: M.solve(x) # función de aplicación
M_inv = spla.LinearOperator((size, size), matvec=M_x)
phi_cg, info = spla.cg(K, b, M=M_inv, tol=1e-8, maxiter=1000)
print('CG convergió en', info, 'iteraciones')
# --------------------------------------------------
# 5. Visualización rápida (requiere matplotlib)
# --------------------------------------------------
import matplotlib.pyplot as plt
phi_grid = phi_cg.reshape((N, N))
plt.imshow(phi_grid, origin='lower', cmap='viridis')
plt.colorbar(label='Potencial')
plt.title('Solución de Poisson (analógica a Maxwell)')
plt.show()
El fragmento anterior resuelve el problema de Poisson, que es la forma escalar de la ecuación de Helmholtz que aparece al eliminar el tiempo en Maxwell bajo condiciones armónicas. Cambiando K por la matriz curl‑curl y añadiendo la constante de onda k, se obtiene la solución de frecuencia de los campos.
6. Comparativa con otras plataformas
| Característica | Python (NumPy/SciPy) | MATLAB | Julia (SparseArrays) |
|---|---|---|---|
| Facilidad de instalación | 🔹 pip/conda, multiplataforma | 🔸 Licencia propietaria | 🔹 Open‑source, paquete Pkg |
| Rendimiento (matrices esparcidas) | 🔹 Optimizado con OpenBLAS, MKL | 🔹 Integrado con Intel MKL | 🔹 Muy cercano a C, compilación JIT |
| Pre‑condicionadores disponibles | 🔹 ILU, AMG (pyamg) | 🔹 ilu, ichol | 🔹 AMG.jl, ILU |
| Paralelismo GPU | 🔹 CuPy, PyTorch, Numba CUDA | 🔹 Parallel Computing Toolbox | 🔹 CUDA.jl |
| Comunidad y soporte | 🔹 Gran comunidad, StackOverflow | 🔹 Soporte oficial, foros | 🔹 Creciente, documentación activa |
7. Buenas prácticas y optimización
- Usar tipos esparcidos desde el inicio: evita conversiones costosas de
numpy.ndarrayascipy.sparse. - Escalar la matriz (pre‑escalado) para mejorar la condición numérica.
- Elección del pre‑condicionador: ILU funciona bien para problemas de Poisson; para Helmholtz se recomiendan AMG o HYPRE.
- Vectorización y uso de BLAS: las operaciones de producto matriz‑vector son críticas; verifica que NumPy esté vinculada a MKL/OpenBLAS.
- Persistencia de la factorización: si resuelves varias cargas con la misma
K, guarda la factorización LU/ILU y reutilízala.
8. Escalabilidad y paralelismo
Para simulaciones de gran escala (>10⁷ DOF) se recomienda:
- Distribuir la matriz con
mpi4pyy usar solvers iterativos paralelos comopetsc4pyopyamgcon MPI. - Ejecutar en GPU mediante
cupyotorch.linalgpara productosAxmasivos. - Utilizar algoritmos de dominio‑descompuesto (Schwarz, FETI) cuando la malla es heterogénea.
Ejemplo rápido de mpi4py con CG:
from mpi4py import MPI
import petsc4py
petsc4py.init()
from petsc4py import PETSc
comm = MPI.COMM_WORLD
A = PETSc.Mat().createAIJ([N,N], comm=comm) # ensamblado distribuido
b = PETSc.Vec().create(comm=comm)
# ... ensamblar A y b ...
ksp = PETSc.KSP().create(comm=comm)
ksp.setOperators(A)
ksp.setType('cg')
ksp.getPC().setType('hypre')
solution = b.duplicate()
ksp.solve(b, solution)
9. Depuración y troubleshooting
- Condición numérica alta: verifica que la malla no tenga elementos muy desproporcionados; aplica escalado diagonal.
- Convergencia lenta de CG: cambia el pre‑condicionador, prueba con
gmreso aumenta la tolerancia. - MemoryError: usa formatos
csrocscy evita matrices densas intermedias. - Resultados no físicos: revisa condiciones de frontera (Dirichlet/Neumann) y unidades de los parámetros.
Conclusiones
Convertir las ecuaciones de Maxwell en sistemas lineales permite aprovechar la madurez del ecosistema de álgebra lineal de Python. Con las herramientas adecuadas (numpy, scipy, petsc4py, cupy) y siguiendo buenas prácticas de escalado, pre‑condicionamiento y paralelismo, es posible simular problemas electromagnéticos de gran escala con un rendimiento comparable a soluciones propietarias.
Álgebra Lineal Aplicada a las Ecuaciones de Maxwell: Algoritmos y Ejemplos en Python