Solver Methods
PhoXonic.jl provides multiple solver methods for different problem sizes:
| Method | Matrix Storage | Algorithm | Best For |
|---|---|---|---|
DenseMethod() | Dense (N×N) | LAPACK eigen | Small/medium systems |
DenseMethod(shift=σ) | Dense (N×N) | LAPACK eigen + filter | 3D (skip spurious modes) |
KrylovKitMethod() | Matrix-free | Arnoldi iteration | Large 2D systems |
KrylovKitMethod(shift=σ) | Dense + LU | Shift-and-invert | 3D, targeted frequencies |
LOBPCGMethod() | Dense (N×N) | Block CG | Symmetric problems |
LOBPCGMethod(shift=σ) | Dense + LU | Shift-and-invert LOBPCG | 3D systems |
Memory and Computational Cost
| Method | Memory | Cost per k-point |
|---|---|---|
| Dense | O(N²) | O(N³) eigendecomposition |
| Matrix-free | O(N) | O(N log N) × iterations |
| Shift-invert | O(N²) | O(N³) LU + O(N²) × iterations |
| LOBPCG | O(N²) | O(N² × nev) × iterations |
N = number of plane waves = basis.num_pw × ncomponents(wave)
For large-scale calculations with N > 10,000, see Matrix-Free Methods.
Recommendations
| Dimension | Wave Type | N (typical) | Recommended Method | Example |
|---|---|---|---|---|
| 1D | Photonic1D | < 100 | DenseMethod() | 301 |
| 1D | Longitudinal1D | < 100 | DenseMethod() | 302 |
| 2D | TEWave/TMWave/SHWave | 100–1,000 | DenseMethod() | 101, 201 |
| 2D | TE/TM/SH | 1,000–10,000 | KrylovKitMethod() or LOBPCGMethod() | 201, 208 |
| 2D | PSVWave | 200–2,000 | DenseMethod() | 201 |
| 2D | PSVWave | > 2,000 | KrylovKitMethod() or LOBPCGMethod() | 201, 208 |
| 3D | TransverseEM | < 2,000 | DenseMethod() (recommended) | 401 |
| 3D | FullVectorEM | < 500 | DenseMethod(shift=0.01) | 402 |
| 3D | FullVectorEM | 500–5,000 | KrylovKitMethod(shift=0.01) or LOBPCGMethod(shift=0.01) | — |
| 3D | FullElastic | < 500 | DenseMethod(shift=0.01) | — |
| 3D | FullElastic | 500–5,000 | KrylovKitMethod(shift=0.01) or LOBPCGMethod(shift=0.01) | — |
Note: For 3D photonic crystals, TransverseEM is strongly recommended over FullVectorEM. See TransverseEM vs FullVectorEM for why.
Note: For 3D phononic crystals, FullElastic is an experimental implementation. See FullElastic (Experimental) for why parameter tuning is challenging.
DenseMethod
The default solver using LAPACK's dense eigenvalue decomposition.
Basic Usage
solver = Solver(TEWave(), geo, (64, 64), DenseMethod(); cutoff=7)DenseMethod for 3D
For small 3D systems, DenseMethod with shift > 0 can be used:
solver = Solver(FullVectorEM(), geo, (12, 12, 12), DenseMethod(shift=0.01); cutoff=5) # Use cutoff≥7 for high εHow it works:
Unlike iterative methods which use shift-and-invert transformation, DenseMethod(shift=σ) computes all eigenvalues using LAPACK, then filters out eigenvalues where ω² < σ. This post-hoc filtering effectively removes the spurious longitudinal modes at ω ≈ 0.
Important difference from iterative methods:
| Aspect | DenseMethod | KrylovKitMethod/LOBPCGMethod |
|---|---|---|
| Computation | All eigenvalues | Only requested number |
| Filtering | Post-hoc (ω² < σ removed) | Shift-and-invert (finds λ closest to σ) |
| Band ordering | Strictly ascending ω | Depends on σ proximity |
| Memory | O(N²) | O(N²) with shift, O(N) without |
This means Dense and iterative methods may return different bands for the same bands=1:n request if there are intermediate eigenvalues. Both results are valid eigenvalues, just in different order.
When to use DenseMethod for 3D:
- Small systems (N × 3 < 2000)
- Validation and debugging
- Need all eigenvalues in ascending order
KrylovKitMethod
Iterative solver based on KrylovKit.jl using Arnoldi iteration.
Basic Usage
solver = Solver(TEWave(), geo, (128, 128), KrylovKitMethod(); cutoff=15)Shift-and-Invert for 3D
For 3D calculations, use shift-and-invert to skip longitudinal modes:
solver = Solver(FullVectorEM(), geo, (16, 16, 16), KrylovKitMethod(shift=0.01); cutoff=7)The shift parameter σ transforms the generalized eigenvalue problem:
| Original | Transformed |
|---|---|
| A x = λ B x | (A - σB)⁻¹ B x = μ x |
| λ = eigenvalue | μ = 1/(λ - σ) |
Eigenvalues closest to σ become the largest |μ|, making them easiest for iterative solvers to find.
Why is Shift Needed for 3D?
The 3D H-field formulation does not explicitly enforce ∇·H = 0, producing:
- Longitudinal modes: λ ≈ 0 (unphysical, violate transversality)
- Transverse modes: λ > 0 (physical electromagnetic waves)
Without shift, iterative solvers converge to the spurious λ ≈ 0 modes first. With shift σ > 0, eigenvalues λ < σ are effectively filtered out.
Choosing σ
| Use case | Recommended σ | Explanation |
|---|---|---|
| Skip longitudinal modes | 0.001 – 0.1 | Must be > 0, but smaller than the smallest physical λ |
| Target specific frequency | ω²_target | Finds modes near the target frequency first |
| Low-frequency modes near k | |k|²/ε_max | Approximate lowest transverse eigenvalue |
Guidelines for choosing σ:
Lower bound: σ must be larger than the longitudinal mode eigenvalues (≈ 0). A small positive value like
σ = 0.01typically works.Upper bound: σ should be smaller than the smallest physical eigenvalue you want to find. For a homogeneous medium: λmin ≈ |k|²/εmax.
Example: |k| = 0.5, ε = 4 → λ_min ≈ 0.0625. Use σ < 0.06.
Safe default: For most 3D photonic calculations,
σ = 0.01works well when |k| ≥ 0.1 (in units of 2π/a).Near k = 0: At the Γ point (k = 0), the lowest transverse modes also have λ → 0. Use a very small shift (σ ~ 0.001) or skip the Γ point.
High-contrast materials: For large ε, eigenvalues scale as 1/ε. Reduce σ proportionally:
σ ≈ 0.01 / ε_max.
Troubleshooting
| Symptom | Likely cause | Solution |
|---|---|---|
| All eigenvalues ≈ 0 | σ too large | Reduce σ |
| Missing low-frequency modes | σ too large | Reduce σ below λ_min |
| Converges to spurious modes | σ too small or = 0 | Increase σ > 0 |
| Slow convergence | σ far from target λ | Adjust σ closer to desired eigenvalues |
Phononic Eigenvalue Scaling
Phononic eigenvalues ω² are typically O(10¹⁰) for common materials (steel, epoxy, etc.), which can cause numerical instability in iterative solvers. PhoXonic.jl automatically scales the eigenvalue problem for SHWave, PSVWave, FullElastic, and Longitudinal1D:
Scaled problem: (A/s) x = (λ/s) B x
where s = c² × |k|² and c² = C₁₁/ρ (longitudinal wave speed squared)This normalizes eigenvalues to O(1), ensuring stable convergence. The scaling is:
- Automatic: No user configuration needed
- Transparent: Results are unscaled before return
- Photonic-safe: Scale factor = 1.0 for TE/TM/FullVectorEM
Example: Steel/epoxy at |k| = 100 rad/m:
- Without scaling: ω² ~ 10¹⁰ → KrylovKit may fail
- With scaling: ω²/s ~ 1 → stable convergence
KrylovKitMethod Parameters
method = KrylovKitMethod(
tol = 1e-8, # Convergence tolerance
maxiter = 300, # Maximum iterations
krylovdim = 30, # Krylov subspace dimension
verbosity = 0, # 0=silent, 1=warnings, 2=info
shift = 0.0, # Spectral shift (0 = no shift)
matrix_free = false # Use matrix-free operators for shift-and-invert
)Matrix-Free Shift-and-Invert
For large 3D calculations where memory is limited, the matrix_free=true option avoids building dense matrices:
# Memory-efficient for large 3D systems
solver = Solver(
FullVectorEM(), geo, (32, 32, 32),
KrylovKitMethod(shift=0.01, matrix_free=true);
cutoff=5
)Comparison:
| Aspect | matrix_free=false (default) | matrix_free=true |
|---|---|---|
| Memory | O(N²) | O(N) |
| Speed | Faster for N < 2000 | Slower (nested iteration) |
| Best for | Small/medium 3D | Large 3D (N > 2000) |
How it works:
- Default mode: Builds dense matrices LHS, RHS, computes LU factorization of (A - σB)
- Matrix-free mode: Uses FFT-based operators and iterative CG solver for inner system
For most 3D calculations with cutoff ≤ 5, the default dense mode is faster. Use matrix_free=true when memory becomes the limiting factor.
LOBPCGMethod
LOBPCG (Locally Optimal Block Preconditioned Conjugate Gradient) is an iterative eigenvalue solver. A. V. Knyazev, SIAM J. Sci. Comput. 23, 517 (2001). DOI:10.1137/S1064827500366124
When to Use LOBPCG
LOBPCG is effective for:
- Large-scale band structure calculations (cutoff ≥ 12)
- Symmetric generalized eigenvalue problems A x = λ B x
- Computing multiple eigenvalues simultaneously (block method)
Performance comparison (2D PSV wave, Steel/Epoxy):
| Cutoff | Matrix Size | Dense | LOBPCG | Speedup | Error |
|---|---|---|---|---|---|
| 8 | 394 | 4.4 s | 11.8 s | 0.37x* | 8.5 rad/s |
| 10 | 634 | 10.1 s | 12.8 s | 0.79x* | 16.2 rad/s |
| 12 | 882 | 21.5 s | 19.8 s | 1.09x | 15.8 rad/s |
| 15 | 1418 | 105.3 s | 50.4 s | 2.09x | 30.2 rad/s |
*Speedup < 1 means Dense is faster
Recommendation:
- cutoff < 10: Use Dense (faster due to BLAS optimization)
- cutoff ≥ 12: Use LOBPCG (speedup increases with problem size)
Basic Usage
# 2D photonic
solver = Solver(TEWave(), geo, (64, 64), LOBPCGMethod(); cutoff=12)
# 2D phononic (LOBPCG effective for large problems)
solver = Solver(PSVWave(), geo, (64, 64), LOBPCGMethod(); cutoff=15)Shift-and-Invert for 3D
For 3D calculations, LOBPCG requires shift > 0 to skip spurious longitudinal modes:
solver = Solver(FullVectorEM(), geo, (16, 16, 16), LOBPCGMethod(shift=0.01); cutoff=7)
solver = Solver(FullElastic(), geo, (16, 16, 16), LOBPCGMethod(shift=0.01); cutoff=5)Why is shift needed for 3D?
The 3D H-field formulation does not enforce the transversality constraint ∇·H = 0, leading to spurious longitudinal modes at λ ≈ 0. Without shift:
- LOBPCG tries to find smallest eigenvalues, converging to these spurious modes
- The RHS matrix B becomes singular in this subspace →
PosDefException
With shift=σ, the problem transforms to (A - σB)⁻¹ B x = μ x, which only returns eigenvalues λ > σ, effectively filtering out the spurious modes.
LOBPCGMethod Parameters
method = LOBPCGMethod(
tol = 1e-3, # Convergence tolerance (default)
maxiter = 100, # Maximum iterations (default)
shift = 0.0, # Spectral shift (required for 3D)
warm_start = true, # Use previous eigenvectors as initial guess
first_dense = true, # Solve first k-point with Dense
)Warm Start for Band Structure Calculations
When computing band structures with compute_bands, LOBPCG uses warm start to speed up calculations. The eigenvectors from the previous k-point serve as the initial guess for the next k-point.
How it works:
- First k-point is solved with
DenseMethod(whenfirst_dense=true) - Subsequent k-points use previous eigenvectors as initial guess
- Since eigenvectors vary smoothly along k-path, LOBPCG converges quickly
Usage:
# Automatic warm start (default)
solver = Solver(PSVWave(), geo, (64, 64), LOBPCGMethod(); cutoff=15)
bands = compute_bands(solver, kpath; bands=1:10)Limitations
- For small problems (cutoff < 10), Dense is faster due to BLAS optimization
- Phononic problems with band crossings at Γ point may require additional care
- Accuracy is typically 10-100 rad/s compared to Dense (0.1-1% relative error)
LOBPCG vs KrylovKit
| Feature | KrylovKitMethod | LOBPCGMethod |
|---|---|---|
| Backend | KrylovKit.jl | IterativeSolvers.jl |
| Algorithm | Arnoldi (Krylov) | Block CG |
| Matrix-free | Yes (2D/3D) | No |
| Phononic scaling | Required | Not required |
| Block computation | No | Yes |
| 3D shift-invert | Yes | Yes |
For details on matrix-free methods and their resolution constraints, see Matrix-Free Methods.
API Reference
- Solver API - DenseMethod, KrylovKitMethod, LOBPCGMethod