Geometry & Dielectric
From a lattice definition to the ε arrays every operator consumes.
Typed lattices, fractional atoms
A crystal enters the solver as a typed lattice (square, rectangular, triangular,
hexagonal, or oblique, or explicit a1/a2 vectors) plus a list of basis atoms with
fractional positions, radii, and per-atom permittivity. The reciprocal basis and the
Brillouin-zone path presets (Γ-X-M-Γ and friends) derive from the same lattice object, so
geometry and k-space can never disagree.
On the board, this is the gray control column on the left: it decides everything and moves almost no data.
Analytic subpixel smoothing
The dielectric build is where accuracy is won. Instead of sampling ε on a grid (staircase artifacts) or averaging over a sub-mesh (slow convergence), Blaze computes the exact circle-pixel intersection for every boundary pixel: filling fraction, interface normal, and interface length. Boundary pixels get the MPB-style anisotropic average
which is the physically correct mixing rule for fields parallel and perpendicular to the interface. This single kernel is why a 32×32 Blaze grid tracks MPB rather than lagging it.
Explore the result at every resolution the site ships (these are real solver outputs):
How close this gets to the reference implementation is easiest to see side by side. The smoothed permittivity maps below compare Blaze2D against MPB across resolutions, with the absolute difference in the bottom row:
What it costs
The build is O(N · atoms) and runs once per job. The output is three real-space arrays of length N = nx·ny: ε(r), ε⁻¹(r), and a 2×2 inverse-tensor field for boundary pixels. There is no dense G-space matrix anywhere; the plane-wave basis is the FFT grid itself.
One honest data-movement note, visible on the board as a blue node: the driver currently clones the ε arrays for every k-point (board). That is an O(N) copy repeated k times where a shared reference would do. It is cheap relative to the solve, but it is exactly the kind of copy the board exists to make visible.
→ Continue with Matrix-Free Operators, which consume these arrays on every application.