Quick start

End-to-end: get a tight-binding Hamiltonian from the API, project it into a near-Fermi subspace, and run a surface Green’s function on the result.

Note

This guide assumes you have tailwater installed (pip install tailwater) and a username/password issued by the Tailwater team. The client talks to the hosted API at https://api.tailwater.io automatically — no configuration needed.

1. Get the artifacts from the API

project=True returns the artifacts you need for fine-tuning and analysis in one credit-billed call:

from pymatgen.core import Structure
from tailwater import tw_api_call

structure = Structure.from_file("MyMaterial.cif")
paths = tw_api_call(
    structure   = structure,
    user        = "acme-research",
    password    = "...",
    output_path = "./outputs",
    filename    = "my_material",
    project     = True,
)
# paths = {"embeddings": "...", "npz": "...", "win": "..."}
#   The Hamiltonian comes back as a sparse SparseHR (wannier90_hr.npz).
#   For small systems (< 30 atoms) it is also auto-converted to a dense
#   HDF5, added under paths["hdf5"] — see :doc:`exporting_models`.

The returned dict always contains a "win" key — the parsed wannier90.win file the server actually ran inference on, useful for tracing graph-construction differences across API and offline runs.

2. (Optional) Fine-tune the heads to fit a near-Fermi window

Fit the output heads to the predicted Hamiltonian’s eigenvalues inside a narrow energy window (a compact, downfolded model), using the bundle’s embeddings.pt + sparse wannier90_hr.npz:

from tailwater import subspace_projection

subspace_projection(
    start_lr     = 1e-4,
    end_lr       = 1e-5,
    num_epochs   = 20,
    energy_range = (-2.0, 2.0),       # eV, relative to E_F
    decay_sigma  = 0.5,
    device       = "cpu",
    save_path    = "./projection_out",
    embed_path   = paths["embeddings"],
    hr_npz_path  = paths["npz"],      # fit the heads to the sparse Hamiltonian
)

After training, ./projection_out/ contains a fine-tuned heads checkpoint, a projected (subspace-restricted) HDF5 model, and a .basis.json describing the orbital basis of the projection.

3. Analyze the model

For semiconductors and insulators, anchor the energy zero to the valence band maximum so every calculator below shares a physically natural reference — see Fermi alignment for semiconductors and insulators for the full guide.

import numpy as np
from tailwater import (
    as_tbmodels, align_to_vbm,
    SurfaceGreensFunction, BulkDOS, bulk_band_structure,
)

model = as_tbmodels(paths["npz"])  # dense tbmodels.Model from the sparse .npz
model = align_to_vbm(model)        # (optional, for non-metals) VBM -> 0

# Bulk DOS (KPM, k-mesh averaged)
dos = BulkDOS(model, k_mesh=(8, 8, 8), energies=(-4, 4)).run()
dos.figure.savefig("bulk_dos.png")

# Surface Green's function (Lopez-Sancho).
# n_jobs=-1 parallelizes across every CPU core — see :doc:`performance`.
sgf = SurfaceGreensFunction(
    model, surface=np.eye(3),
    energies=np.linspace(-1, 1, 201),
    k_path=[[0, 0.5, 0], [0, 0, 0], [0.333, 0.333, 0]],
    k_labels=["M", r"$\Gamma$", "K"],
    n_jobs=-1,
).run()
sgf.figure_top.savefig("surface_top.png")

# Bulk band structure (auto k-path via seekpath, if installed)
fig = bulk_band_structure(model, auto=True, structure=structure,
                          spacing=0.02, e_range=(-3, 3))
fig.savefig("bands.png")

# 2D Fermi-arc / constant-energy surface map at E = 0
# See :doc:`fermi_arcs` for the full guide.
from tailwater import FermiArcMap
arc = FermiArcMap(
    model, surface=np.eye(3),
    energy=0.0, Nx=40, Ny=40,
    thickness=6, NN=5, eps=0.005,
    n_jobs=-1,
).run()
arc.figure_top_interpolated.savefig("fermi_arc_top.png")