Adding a new interface
How to connect a new data source or a new projection to unfolding.
This page mirrors the contributor recipe in the repository
(devdoc/adding-an-unfolding.md), which is the canonical, most detailed
version with code snippets and a worked example
(tests/test_toy_tb_fixture.py).
Decide what you are adding
A new source (a code or file format that produces bands on a supercell): write a parser plus a thin adapter, reuse one of the existing engines, and register a route. A parser alone is never runnable: every engine consumes a normalized in-memory contract (layout, units, gauge), and producing that contract is the adapter โ typically tens of lines of glue plus unit/gauge conversion, never new weight math.
A new projection (a genuinely different weight definition, e.g. a
new quasiparticle type): write one kernel module. Consume the shared
cosets and degeneracy helpers and keep the kernel engine-local.
Pick the engine your source feeds
| Your source provides | Engine to reuse | Input contract |
|---|---|---|
| Plane-wave eigendata (G-vectors + coefficients) | pw_unfolder.PWUnfolder | PWEigenData: kpoints (nk,3) supercell fractional; per k gvecs[ik] (npw,3) int, coefficients[ik] (nspin,nband,nspinor,npw) complex, eigenvalues[ik] (nspin,nband) in the source unit |
| An LCAO supercell Hamiltonian | lcao_unfolder.LCAOUnfolder | model exposing atoms, HR/SR dicts {R: (norb_sc, norb_sc)} and hs_and_eigen(k); relabel via mapping.RelabelMap.from_atoms(...) |
| Phonons on a stored q-grid | phonon_unfolder.phonon_unfolder | atoms, supercell_matrix, eigenvectors (evecs[iq, :, ibranch] per mode), qpoints (supercell frame); robust weights via `get_weights_robust(frequencies, gauge=“fold” |
| Magnon eigendata (BdG) | magnon_unfolder.MagnonUnfolder | MagnonEigenData: kpoints (nk,3), energies (nk,nmode), wavefunctions (nk,nmode,2nmag) canonical Euclidean amplitudes, positions (nmag,3) supercell fractional |
| Stored-k reference bands with projectors | adapter-local physics (the PAW precedent) | parse supercell + primitive reference + projectors yourself; use degeneracy.assign_eigvalsh_groups(..., descending=False) |
Conventions that bite
- Row convention:
K = k @ M.Teverywhere (A_sc = M @ A_prim). - Eigenvector layout: engines consume
evecs[ik]with rows = DOF, modes as columns. - Units: the
Datasetboundary is eV. Convert beforeDataset.from_result, or passenergy_unittosave_dataset(shipped routes use"cm^-1"for phonons,"meV"for magnons). - Fermi metadata: either absolute energies plus
fermi_energy=<eV>withenergy_reference="absolute", or pre-shifted energies withenergy_reference="fermi"โ never both. - Normalization: LCAO dual-basis weights need the overlap metric; BdG magnons are g-orthogonal (group-total presentation); plane-wave/character weights are Euclidean.
- Gauge phases:
exp(ยฑ2ฯi kยทr)signs differ per module by design; a folded-path engine distinguishes coset members withk* = fold(k) โ K_storedwithout mod 1. - Optional backends: lazy imports raising
ImportErrornamingpip install unfolding[<extra>]; the core stays backend-free.
Register a route (TOML + CLI + JSON for free)
Three edits, all derived from one dataclass so they cannot drift:
- A config dataclass in
unfolding/config.py: one field per parameter withF(section, kind, help)metadata; sections areinput, structure, path, options, output; field kindspath,out,str,int,float,bool,matrix(3ร3 ints),kpoints,float-list,str-list. Add it to__all__and theRouteConfigunion. - A runner
run_<name>(cfg)inunfolding/routes.py: resolve the path with_resolve_path, call the adapter withreturn_result=True, save the dataset with_save_data, the figure with_save. - One
RouteSpecline inconfig.ROUTES(the runner import goes in the deferred import block at the bottom ofconfig.py).
unfolding <route> --help, TOML loading, and the --data JSON output
then exist with no further code. Export the engine classes eagerly in
unfolding/__init__.py (backend-free) and adapter functions through
_LAZY_EXPORTS.
Verify
- Engine/adapter: pristine supercells give binary 0/1 weights on the commensurate grid; plant a near-degenerate pair at a generic k and check your group strategy.
- Dataset:
Dataset.from_resultโsave_datasetโload_datasetround-trip preserves shapes, units metadata, Fermi convention, and fold/sc k-points;plot_datasetrenders. - Route: one TOML through
unfolding --configand one flags invocation produce identical weights (the shipped pattern istests/test_routes_backends.py::test_siesta_wfsx_parity);import unfoldingstill works without the optional extra.
For a runnable example, see the repository recipe
(devdoc/adding-an-unfolding.md) and its worked nearest-neighbour
tight-binding fixture; example data handling (committed manifests vs
the external runtime store) is described on the repository page
devdoc/example-data.md.