User Guide: CFFT Acceleration

Polyorder.jl supports CFFT (Crystallographic FFT) Acceleration for SCFT simulations of ordered phases with crystallographic symmetry. Unlike DCT acceleration (limited to 12 mirror-symmetric space groups), CFFT works for all 230 space groups, including Gyroid ($Ia\bar{3}d$, SG 230). For technical details, see CFFT Acceleration.

Quick Start

Prerequisites

Install CrystallographicFFT.jl:

using Pkg
Pkg.add("CrystallographicFFT")

Enabling CFFT

Option A — High-level (recommended)

Pass use_cfft=true to the NoncyclicChainSCFT constructor:

using Polyorder, Polymer, Scattering, CrystallographicFFT

# 1. Define system
sys = AB_system(χN=20.0, fA=0.5)

# 2. Create lattice WITH space group
uc = UnitCell(Cubic(), 6.3)
lat = BravaisLattice(uc, 221)  # Pm-3m

# 3. Create SCFT: CFFT field is created automatically
scft = NoncyclicChainSCFT(sys, lat; ds=0.01, mde=OSF,
                           use_cfft=true, profile=:balanced)

# 4. Solve as usual
Polyorder.solve!(scft)

Option B — Lower-level (for more control)

Construct a CFFT field manually and pass it to the constructor:

using Polyorder, Polymer, Scattering, CrystallographicFFT

sys = AB_system(χN=20.0, fA=0.5)
uc = UnitCell(Cubic(), 6.3)
lat = BravaisLattice(uc, 221)

# Create CFFT field — sg_num is read from lattice automatically
w_cfft = cfft_auxiliaryfield(lat; spacing=0.15)

# automatically use the CFFT solver when passing a CFFT field
scft = NoncyclicChainSCFT(sys, w_cfft; ds=0.01, mde=OSF, profile=:balanced)
Polyorder.solve!(scft)
Note

CrystallographicFFT.jl must be loaded (using CrystallographicFFT) before calling cfft_auxiliaryfield or using use_cfft=true. Otherwise, a clear error message is shown.

No code changes are needed in the solve loop — the solver automatically uses CFFT internally.

Verifying CFFT is Active

# Check field type
println(typeof(scft.wfields[1]))
# → CFFTAuxiliaryField{Float64, 3, ...}

# Check subgrid vs full grid
w = scft.wfields[1]
println("Subgrid: ", size(w.data))     # e.g., (12, 12, 12)
println("Full grid: ", w.N_full)        # e.g., (24, 24, 24)

Performance

Memory & Speed

CFFT operates on a subgrid $M^3 = (N/L)^3$, reducing both memory and computation:

ComponentFFT Mode (Full Grid)CFFT Mode (Subgrid)Savings
Solver Buffers$N^3$$M^3 = N^3/L^3$$L^3\times$ (8× for $L=2$)
Spectral Buffer$N^3$ complex$n_\text{spec}$ complex~58× for Pm-3m
MDE Step Speed$O(N^3 \log N)$$O(M^3 \log M)$~8× for $L=2$

With SymmetricStorage (:balanced profile)

Combining CFFT with SymmetricStorage gives maximum compression:

Space Group$L$StarsTotal CompressionMemory Saved
Pm-3m (221)2subgrid-compressed~40×~97%
Ia-3d (230)21,156 (for 48³)~96×~99%

Supported Space Groups

CFFT supports all 230 space groups. Key examples:

PhaseSpace GroupSG #Supported?
LamellarPmmm47✅ (CFFT and DCT)
Hexagonal CylinderP6/mmm191✅ (CFFT only)
Simple CubicPm-3m221✅ (CFFT and DCT)
FCC SphereFm-3m225✅ (CFFT and DCT)
BCC SphereIm-3m229✅ (CFFT and DCT)
GyroidIa-3d230✅ (CFFT only)
σ-PhaseP4₂/mnm136✅ (CFFT only)
Tip

For Gyroid (SG 230), CFFT is the only available spectral acceleration. DCT cannot handle glide planes.

GPU Support

CFFT fields work on GPU with no code changes beyond loading CUDA:

using CUDA

# Option A: High-level
scft = NoncyclicChainSCFT(sys, lat; ds=0.01, mde=OSF,
                           use_cfft=true, array_type=CuArray{Float64},
                           profile=:balanced)

# Option B: Manual field
w = cfft_auxiliaryfield(lat; spacing=0.15, array_type=CuArray{Float64})
scft = NoncyclicChainSCFT(sys, w; ds=0.01, mde=OSF, profile=:balanced)

GPU performance has been validated on NVIDIA RTX 2080 Ti with Ia-3d (SG 230):

  • CPU vs GPU free energy: $|\Delta F| < 10^{-11}$
  • GPU iteration: ~311 ms vs CPU ~378 ms (for $N=48^3$)
  • VariableCell: Full GPU support including stress tensor and grid resize

Spectral Updaters with CFFT

All SpectralSCFTAlgorithm updaters work with CFFT fields — both on CPU and GPU:

# SIS (semi-implicit, most commonly used as preconditioner)
solve!(scft, SIS(0.5))

# ETD (exponential time differencing)
solve!(scft, ETD(1.0))

# Anderson with SIS preconditioner (recommended)
solve!(scft, Anderson(SIS(0.5)))

# Full-matrix variants
solve!(scft, SISF(0.5))
solve!(scft, ETDF(1.0))

# Predictor-corrector variants
solve!(scft, PO(0.5))
solve!(scft, ETDPEC(1.0))

The CFFT path is automatically selected when scft.wfields[1] is a CFFTAuxiliaryField. No additional configuration is needed.

Tip

For production runs, use Anderson(SIS(0.5)) — the Anderson acceleration operates in real space on the subgrid, while the SIS preconditioner uses the CFFT spectral path for optimal convergence.

Cell Optimization

CFFT is fully compatible with variable-cell SCFT (cell_solve!). The stress tensor is computed natively in the spectral ASU — no full-grid expansion needed. When the unit cell changes:

  1. The lattice is updated in-place
  2. cfft_kk_orbsum is recomputed with the new lattice matrix
  3. MDE diffusion kernels () are recalculated

This happens automatically inside the reset! function.

Resize Hysteresis

During variable-cell optimization, the grid size $N$ may oscillate if the cell edge is near a rounding threshold of best_N_fft. CFFT includes automatic hysteresis to prevent this:

  • Grid growth is always accepted
  • Grid shrinkage is only accepted if $N$ drops by more than $4L$ per dimension (four subgrid points)

This prevents expensive plan rebuilds from being triggered by rounding-level fluctuations in the cell edge. For example, with SG 221 ($L=2$), a shrinkage of $N$ from 56 to 54 (subgrid 28→27) is suppressed, while a shrinkage from 56 to 48 is accepted.

CFFT vs DCT: When to Use Which?

CriterionChoose DCTChoose CFFT
Space group has only mirror planes✅ Prefer DCT✅ Also works
Space group has glide/screw❌ Cannot use✅ Required
Gyroid (SG 230)✅ Only option
Custom VkDCT GPU kernel available✅ Fastest✅ Good
Non-orthogonal lattice✅ Supported

In general: choose CFFT if possible, especially non-symmorphic groups. DCT + VkDCT may have some speed advantage for low symmetry groups (but it uses full grid to compute density field and stresses, etc. which is slower than CFFT and consumes more memory).

Troubleshooting

"DimensionMismatch" during construction

Ensure your N dimensions are compatible with the space group. Use CrystallographicFFT.recommended_N:

using CrystallographicFFT
N_rec = recommended_N(sg_num, 64)  # Suggest compatible N near 64

SCFT diverges with CFFT

  1. Check symmetrization: Initial fields must be symmetrized. Use init=:randn (default) which auto-symmetrizes, or manually call symmetrize_field!.
  2. Check Anderson norm: If using Anderson acceleration, ensure the LinearAlgebra.norm override is active (loaded via the CFFT extension automatically).
  3. Reduce ds: Try ds=0.005 or smaller to check time-step stability.

Comparing CFFT with FFT results

To verify CFFT correctness against FFT:

# Create both solvers with identical initial fields
scft_fft = NoncyclicChainSCFT(sys, lat; ds=0.01, mde=OSF, init=:none)
scft_cfft = NoncyclicChainSCFT(sys, lat; ds=0.01, mde=OSF, init=:none, use_cfft=true)

# Set identical wfields (symmetrized)
# ... see test/cfft_vs_fft_onestep.jl for full procedure

Spectral updater diverges with CFFT on GPU

If a spectral updater (SIS, ETD, etc.) crashes on GPU with KernelError: passing non-bitstype argument, ensure you are using the latest version of PolyorderCFFTExt — earlier versions allocated CPU buffers for spectral operations. The current implementation uses KernelAbstractions.allocate for all spectral work buffers.

Grid size oscillation during VariableCell

If you see repeated "Space resolution changed" messages alternating between two sizes (e.g., (27,27,27) ↔ (28,28,28)), the hysteresis threshold may need adjustment. The default threshold requires shrinkage > $4L$ per dimension. You can verify by checking the actual $N$ vs $M$ sizes:

w = scft.wfields[1]
println("N_full = ", w.N_full, ", M = ", size(w.data), ", L = ", stride_factors(w.cfft_pair))