mag4

mag4 — Heisenberg J extraction (four-state method) and multi-sublattice LSWT.

Convention

Hamiltonian: H = +Σ_{i≠j} J_ij S_i·S_j (AFM: J > 0, FM: J < 0).

Four-state energies of one dimer give:

JS² = + ( E_uu + E_dd − E_ud − E_du ) / 4    [meV·S²]

Public API

The most useful entry points are re-exported here so that simple scripts can do:

from mag4 import (
    crystal_from_cif, run_cif_analysis, expand_supercell,
    find_dimers, build_reference_bath, generate_four_states,
    find_unique_configs, find_symmetry_ops,
)

Sub-packages mag4.magnon, mag4.analysis and mag4.cli host the spin-wave theory, the geometric analyses and the command-line entry points respectively.

class mag4.CrystalData(cell, all_species, target_species, nb_species, total_atoms, elements, lattice_matrix, lattice_vectors)

Bases: tuple

all_species

Alias for field number 1

cell

Alias for field number 0

elements

Alias for field number 5

lattice_matrix

Alias for field number 6

lattice_vectors

Alias for field number 7

nb_species

Alias for field number 3

target_species

Alias for field number 2

total_atoms

Alias for field number 4

class mag4.Dimer(idx_a, idx_b, label_a, label_b, distance, coupling_name)

Bases: tuple

coupling_name

Alias for field number 5

distance

Alias for field number 4

idx_a

Alias for field number 0

idx_b

Alias for field number 1

label_a

Alias for field number 2

label_b

Alias for field number 3

class mag4.FourStateConfig(idx, coupling_name, state_label, spin_vector, dimer)

Bases: tuple

coupling_name

Alias for field number 1

dimer

Alias for field number 4

idx

Alias for field number 0

spin_vector

Alias for field number 3

state_label

Alias for field number 2

mag4.crystal_from_cif(cif_path, element)[source]

Build a CrystalData directly from a CIF file (no POSCAR needed).

Parameters:
  • cif_path (str) – Path to the CIF file.

  • element (str) – Magnetic element symbol (e.g. "Gd").

Raises:

ValueError – If element is not present in the structure.

Return type:

CrystalData

mag4.read_poscar(poscar, species_index)[source]

Parse a VASP POSCAR / CONTCAR file.

Parameters:
  • poscar (str) – Path to the POSCAR.

  • species_index (int) – 1-based index of the magnetic species in the POSCAR header.

Return type:

CrystalData

mag4.write_poscar(crystal, name)[source]

Write a single POSCAR for the supercell geometry. Returns the file path.

Return type:

str

Parameters:
mag4.get_mm_distances(cif_path, element, cutoff)[source]

Return sorted list of unique M–M pairs within cutoff Å.

Each entry is a dict with keys site_i, site_j, label_i, label_j, image, distance.

Return type:

list

Parameters:
mag4.group_inequivalent(results, tol=0.01)[source]

Cluster M–M pairs into distance shells (J1, J2, …).

Returns a list of dicts {label, distance, dist_min, dist_max, multiplicity, pairs}.

Return type:

list

Parameters:
mag4.get_distance_shells(cif_path, element, cutoff, tol=0.01)[source]

Lightweight version of group_inequivalent() for the magnon pipeline.

Returns [{"label": "J1", "distance": d_mean, "n_bonds": k}, …].

Return type:

List[Dict]

Parameters:
mag4.lattice_matrix(a, b, c, alpha_deg, beta_deg, gamma_deg)[source]

Build a 3×3 lattice matrix (columns = a, b, c) from cell parameters.

Parameters:
  • a (float) – Lattice parameters in Å.

  • b (float) – Lattice parameters in Å.

  • c (float) – Lattice parameters in Å.

  • alpha_deg (float) – Cell angles in degrees.

  • beta_deg (float) – Cell angles in degrees.

  • gamma_deg (float) – Cell angles in degrees.

Returns:

Lattice matrix with the three lattice vectors as columns.

Return type:

ndarray, shape (3, 3)

mag4.frac_to_cart(frac, lat_mat)[source]

Convert fractional → Cartesian coordinates.

Return type:

ndarray

Parameters:
mag4.get_cartesian_coords(atoms, lat_mat)[source]

Cartesian coordinates of a list of atoms in the legacy [label, x, y, z, …] format.

Return type:

ndarray

Parameters:
mag4.expand_supercell(crystal, na, nb, nc)[source]

Expand a CrystalData by (na × nb × nc).

Returns the original instance unchanged when na = nb = nc = 1.

Return type:

CrystalData

Parameters:
mag4.check_coupling_spacing(couplings, user_tol)[source]

Inspect spacing between consecutive J distances and return a safe dimer-search tolerance.

Warns when:

  • --tol is larger than half the minimum spacing (two J shells could share a dimer).

  • Two consecutive J values are closer than 2*DIST_EQUIV_TOL (probably the same shell).

When two adjacent couplings are intentionally at the same distance (e.g. sub-shells J1a / J1b produced by geometry-aware grouping — distinguished by their bridging-angle fingerprint, not by distance), the zero gap between them is not a noise issue: it is dropped from the warning and from the safe_tol computation so the dimer search can still use a non-zero distance tolerance. The angle filter in find_dimers() keeps the sub-shells apart.

Return type:

float

Parameters:
mag4.find_dimers(atoms, lat_mat, couplings, tol, verbose=True, *, structure=None, ligand_elements=None, bond_cutoff=2.5, angle_tol=1.0, ll_cutoff=0.0, signed_dihedral=False)[source]

Find all intra-cell atom pairs for each coupling distance ± tol.

Set verbose=False to silence info logs and the missing-coupling warning block; useful when scanning many trial supercells.

Return type:

Dict[str, List[Dimer]]

Parameters:

Design principle

The four-state method requires that the two atoms of a dimer be DISTINCT sites in the working cell so that they can be assigned independent spins. Using periodic images to “find” a coupling would conflate atom j with its image j + (h, k, l), which is already a different atom in the supercell.

The correct workflow is therefore:

  1. Build a supercell large enough that every coupling distance d_Jn appears as a genuine intra-cell pair (direct Cartesian distance).

  2. Use plain scipy.spatial.distance.cdist() here — no wrapping, no image search.

If a coupling is not found as a direct pair the cell is too small along one axis. The function diagnoses which axis and tells the user exactly which --supercell flag to try.

Angle-aware mode

When structure (a pymatgen.core.Structure of the same supercell, with the magnetic atoms placed at indices 0..N_mag-1 — see mag4.geometry.crystal_to_pymatgen()) and ligand_elements are supplied, each couplings entry must be a triple [label, distance, fingerprint]. A candidate pair is then accepted only when both its distance and its M–L–M bridging-angle fingerprint match the target. This lets the four-state pipeline distinguish J shells that are degenerate in distance but route super-exchange through different bridging geometries.

mag4.find_symmetry_ops(cart_coords, lat_vecs, tol=0.05, *, crystal=None)[source]

Find site-permutation symmetry operations of the magnetic sublattice.

Candidate operations (rotation + translation pairs) come from spglib, so any axis orientation and any space group is handled — the result no longer depends on which lattice vector is the stacking axis. Each candidate is then verified site-by-site (tolerance tol Å) and returned as a permutation tuple; perm[i] is the index of the site to which site i is mapped.

Parameters:
  • cart_coords (Cartesian coordinates of the magnetic sites.)

  • lat_vecs (3×3 lattice matrix, rows = a, b, c.)

  • tol (site-matching tolerance in Å (also spglib’s symprec).)

  • crystal (optional mag4.constants.CrystalData of the full) – structure. When given, symmetry is computed on the full decorated crystal (magnetic + non-magnetic atoms, distinguished by element), so every returned operation is a true crystal symmetry — configurations it merges are guaranteed degenerate. When omitted, only the bare magnetic sublattice is analysed (its symmetry can be higher than the crystal’s; combine with mag4.geometry.filter_symops_by_fingerprint() if ligand environments matter).

Return type:

List[Tuple[int, ...]]

mag4.build_reference_bath(n_atoms, user_ref=None)[source]

Build the reference magnetic bath.

Default behaviour is fully ferromagnetic (all +1). Pass user_ref as a list of ±1 of length n_atoms to override.

Raises:

ValueError – If the length is wrong or values other than ±1 are present.

Return type:

list

Parameters:
mag4.generate_four_states(ref_bath, dimer, coupling_name)[source]

Generate the four spin configurations uu, dd, ud, du for one dimer.

Only the two atoms of the dimer differ between configurations; the rest of the magnetic bath is frozen to ref_bath.

Return type:

List[FourStateConfig]

Parameters:
mag4.find_unique_configs(all_configs, sym_ops, *, time_reversal=True)[source]

Group configurations equivalent under the magnetic (grey) group.

The group is the unitary crystal operations sym_ops plus, when time_reversal is True (the default), each of them composed with time reversal T (global spin flip) — exact in a collinear calculation without spin-orbit coupling. Returns a list of groups; the energy of any member of a group can be used interchangeably in the J extraction formulas.

time_reversal=False restricts to the unitary subgroup — used by --check-degeneracy to keep the T-redundant configurations as separate DFT runs whose energy agreement then tests the antiunitary symmetry (and the completeness of the spin model).

Return type:

List[List[FourStateConfig]]

Parameters:
mag4.run_cif_analysis(cif_path, element, cutoff, tol=0.01, *, ligand_elements=None, bond_cutoff=2.5, angle_tol=1.0, ll_cutoff=0.0, signed_dihedral=False)[source]

Print distance / J tables and return (crystal, couplings).

couplings is a list of items that can be fed straight into mag4.dimers.find_dimers():

  • Without ligand_elements: [[label, distance], ...] (legacy form, distance is the only discriminator).

  • With ligand_elements set: [[label, distance, fingerprint], ...] where fingerprint is an mag4.geometry.AngleFingerprint carrying the M–L–M bridging geometry — used by find_dimers as a secondary discriminator so that pairs at the same distance but different bridging angles end up in different J shells (J1a, J1b, …).

Return type:

tuple[CrystalData, list]

Parameters:
mag4.magmom_to_udn(values)[source]

Translate a flat list of MAGMOM values to the u/d/n letters.

Parameters:

values (sequence of float) – One value per atom in POSCAR order. Sign carries the spin direction; zero means non-magnetic.

Returns:

One letter per atom: "u" for positive, "d" for negative, "n" for zero.

Return type:

list of str

Examples

>>> magmom_to_udn([-7, 7, 7, -7, 7, 7, 7, 0, 0, 0, 0])
['d', 'u', 'u', 'd', 'u', 'u', 'u', 'n', 'n', 'n', 'n']
mag4.build_udn_lines(unique_groups, crystal, spin_magnitude=1.0)[source]

Per-unique-config list of u/d/n letters in POSCAR order.

Returns a list of length len(unique_groups); each element is the list of u/d/n letters for one configuration. Order matches mag4.vasp.build_magmom_lines().

Return type:

List[List[str]]

Parameters:
mag4.find_instgen()[source]

Locate the WIEN2k instgen script.

Lookup order:

  1. $WIEN2k_INSTGEN environment variable, if set.

  2. instgen on $PATH.

  3. instgen_lapw on $PATH (the canonical WIEN2k name).

  4. $WIENROOT/instgen_lapw.

  5. ~/bin/instgen.

Returns the absolute path or None if not found.

Return type:

Optional[str]

mag4.write_case_inst_files(unique_groups, crystal, parent_dir, instgen_path=None, spin_magnitude=1.0)[source]

For every unique configuration, write a configN.inst file in configN/.

The output file is named after the directory (config1.inst inside config1/, etc.) so that WIEN2k’s standard case == basename(pwd) convention works directly on the user’s HPC machine.

Parameters:
Returns:

Paths of the written configN.inst files, in config order.

Return type:

list of str

mag4.write_case_struct_files(crystal, n_configs, parent_dir, *, rmt=2.0, npt=781, r0=None)[source]

Write configN.struct into every configN/ subdirectory.

The cell and atom list are identical across configurations; only the spin pattern (in configN.inst) differs. We therefore write the same struct content N times, naming each file after its directory.

Return type:

List[str]

Parameters:
mag4.write_struct_file(crystal, path, *, title=None, rmt=2.0, npt=781, r0=None, calc_mode='RELA', lattice=None)[source]

Write a complete WIEN2k case.struct from a CrystalData.

Every atom is emitted as a separate inequivalent site (MULT=1) with a unique label of the form <element><n> ("Cs1", "V 9", "I17" …). Only the identity symmetry operation is written, so WIEN2k will not merge or reorder atoms. This guarantees that the atom order in configN.struct matches the order assumed by configN.inst.

See write_struct_file_symmetric() for the spin-aware variant that uses real-space symmetry (default in the CLI; this function is kept as a fallback behind --no-symmetrize-struct).

Parameters:
  • crystal (CrystalData) – The (super-)cell as produced by mag4.supercell.

  • path (str) – Where to write the struct file (full path including filename).

  • title (Optional[str]) – Free-text title written on line 1. Defaults to a mag4-tagged note.

  • rmt (Union[float, Dict[str, float]]) – Muffin-tin radius in bohr. Either a single value applied to every element (default 2.0) or a dict {element_symbol: value}.

  • npt (int) – Number of radial mesh points (default 781; must be odd).

  • r0 (Union[float, Dict[str, float], None]) – First radial mesh point in bohr. None (default) → picked per-Z via _default_r0(); a single float applies to all elements; a dict allows per-element overrides.

  • calc_mode (str) – "RELA" (relativistic, default) or "NREL" (non-relativistic).

  • lattice (Optional[str]) – WIEN2k single-letter lattice code. Auto-detected from the cell angles when None.

Return type:

None

Notes

The per-atom header line follows the format declared in the WIEN2k user guide: (A10, 5X, I5, 5X, F10.8, 5X, F10.5, 5X, F10.5) for name, NPT, R0, RMT, Z respectively. The 5X gaps carry decorative text (" NPT=", "  R0=", " RMT=", "   Z:") for readability — WIEN2k skips them when parsing.

class mag4.EnergyResult(energy_eV, converged, source, n_iter=0, diagnostics=<factory>, last_dis=None, last_dis_line=None, last_ene_line=None)[source]

Bases: object

Outcome of parsing one DFT output file.

Parameters:
  • energy_eV (float | None)

  • converged (bool)

  • source (str)

  • n_iter (int)

  • diagnostics (List[str])

  • last_dis (float | None)

  • last_dis_line (str | None)

  • last_ene_line (str | None)

energy_eV

Last total energy reported in the file, expressed in eV. None when no energy could be parsed (typically: the calculation never produced one — crashed early, file truncated, etc.).

Type:

float or None

converged

Coarse convergence flag (see backend-specific notes in extract_vasp_energy() / extract_wien2k_energy()).

Type:

bool

source

Absolute path of the file that was parsed.

Type:

str

n_iter

Number of (electronic) iterations detected. 0 if unknown.

Type:

int

diagnostics

Free-form one-line warnings / notes the caller may want to surface.

Type:

list of str

energy_eV: float | None
converged: bool
source: str
n_iter: int = 0
diagnostics: List[str]
last_dis: float | None = None

Last :DIS (charge distance) numerical value, in WIEN2k units. Populated only for WIEN2k; None for VASP or when no :DIS line was matched. Useful as a tight convergence check the user can eyeball.

last_dis_line: str | None = None

Verbatim :DIS line from the SCF file (last occurrence).

last_ene_line: str | None = None

Verbatim :ENE line whose value was used as energy_eV.

mag4.extract_vasp_energy(outcar_path)[source]

Parse a VASP OUTCAR and return its converged total energy.

Convergence rule (simple, code-agnostic):

converged is True when all of the following hold:

  • the file exists and is readable,

  • at least one free  energy  TOTEN line was parsed,

  • no WARNING: ... NELM = N line was emitted,

  • the SCF actually converged — either VASP’s electronic marker aborting loop because EDIFF is reached or the ionic reached required accuracy appears — or the run finished cleanly (Voluntary context switches:).

The clean-finish marker is sufficient but not required: a job OOM-killed or timed out during the final clean-up drops that tail without invalidating the already-converged energy, so the EDIFF marker rescues it (see diagnostics).

Tighter convergence checks (per-element forces, stress, EDIFF / EDIFFG numerical thresholds) are intentionally NOT enforced here — they vary per project; see diagnostics for the raw flags so the caller can decide what to do.

Returns:

Always non-None; check .energy_eV and .converged to interpret the outcome.

Return type:

EnergyResult

Parameters:

outcar_path (str)

mag4.extract_wien2k_energy(scf_path)[source]

Parse a WIEN2k SCF file and return its converged total energy in eV.

Return type:

EnergyResult

Parameters:

scf_path (str)

Convergence rule (simple):

converged is True when all of the following hold:

  • the file exists and is readable,

  • at least one :ENE line was parsed,

  • at least one :ITE…: iteration block was detected,

  • the energy was emitted by the last iteration block of the file (i.e. the calculation didn’t crash mid-iteration).

This does not enforce a numeric threshold on :DIS (charge distance) — the value is surfaced in EnergyResult.last_dis and the verbatim :DIS line in EnergyResult.last_dis_line so the user can eyeball that the SCF converged tightly enough.

Conversion: E [eV] = E [Ry] × 13.605693122994.

mag4.get_energy(config_dir, code, scf_basename=None)[source]

Convenience dispatcher: parse the right output for the requested code.

Parameters:
  • config_dir (str) – Path to a single configN/ directory.

  • code ({'vasp', 'wien2k'}) –

    Which output file to look for inside config_dir:

    • vaspOUTCAR

    • wien2k → SCF file (see scf_basename).

  • scf_basename (str, optional) – WIEN2k only. Basename (with or without the .scf suffix) of the SCF file to parse inside config_dir. Useful when several SCF runs are stored side by side under names like pbe.scf, r2scan.scf. When omitted (default) the standard WIEN2k convention <basename(config_dir)>.scf is used — i.e. config5/config5.scf for config5/.

Return type:

EnergyResult

class mag4.Formula(name, distance_A, config_uu=None, config_dd=None, config_ud=None, config_du=None, incomplete=False, divisor=4, raw='')[source]

Bases: object

One row of the EXTRACTION FORMULAS section.

Parameters:
  • name (str)

  • distance_A (float)

  • config_uu (int | None)

  • config_dd (int | None)

  • config_ud (int | None)

  • config_du (int | None)

  • incomplete (bool)

  • divisor (int)

  • raw (str)

name

Coupling label as printed in the report, e.g. "J1".

Type:

str

distance_A

Mean M–M distance of the shell in Å.

Type:

float

config_uu, config_dd, config_ud, config_du

Unique-configuration indices (1-based, matching the configN/ directory names) whose energies are combined as (E_uu + E_dd E_ud E_du) / 4.

Type:

int

incomplete

True when the report flagged this shell as INCOMPLETE (cell too small to give a distinct dimer). In that case the four config indices are unset and J cannot be computed.

Type:

bool

raw

Original text of the line, kept for traceability.

Type:

str

name: str
distance_A: float
config_uu: int | None = None
config_dd: int | None = None
config_ud: int | None = None
config_du: int | None = None
incomplete: bool = False
divisor: int = 4
raw: str = ''
property config_indices: List[int]

Return the four config indices in (uu, dd, ud, du) order.

class mag4.JResult(name, distance_A, formula, energies_eV=<factory>, JS2_eV=None, JS2_meV=None, J_meV=None, J_K=None, error=None)[source]

Bases: object

One row of the final J table emitted by compute_j_values().

Parameters:
name

Coupling label ("J1" …).

Type:

str

distance_A

Mean M–M distance of the shell, Å.

Type:

float

formula

The original parsed formula (provides config indices, raw text).

Type:

Formula

energies_eV

Per-state total energies in eV. Keys: "uu", "dd", "ud", "du". None when the corresponding config did not converge (or didn’t exist).

Type:

dict[str, float or None]

JS2_eV, JS2_meV

Computed JS² = (E_uu + E_dd E_ud E_du) / 4. None if the formula could not be evaluated (any energy missing).

Type:

float or None

J_meV, J_K

Computed J = JS² / in meV and Kelvin (J / k_B). Only populated when a positive spin S was passed.

Type:

float or None

error

Human-readable reason if JS2_eV is None (which configs failed, INCOMPLETE shell, …).

Type:

str or None

name: str
distance_A: float
formula: Formula
energies_eV: Dict[str, float | None]
JS2_eV: float | None = None
JS2_meV: float | None = None
J_meV: float | None = None
J_K: float | None = None
error: str | None = None
property computed: bool

True iff JS² could be computed (all 4 energies present).

mag4.parse_report_formulas(report_path)[source]

Extract every J = ( E[#…] + …) / 4 line from a report file.

Parameters:

report_path (str) – Path to a four_state_report.dat produced by mag4-magnetic.

Returns:

One entry per coupling shell discovered in the report, in the order they appear. Shells flagged INCOMPLETE in the report are included with incomplete=True (so the caller can warn about them) but with no config indices.

Return type:

list of Formula

Raises:
  • FileNotFoundError – If report_path doesn’t exist.

  • ValueError – If the file exists but contains no recognisable formula or INCOMPLETE line — most likely the wrong file was passed.

mag4.compute_j_values(formulas, energies, spin=None, config_n_atoms=None, n_supercell_atoms=None)[source]

Apply each formula to the matching energies and return the J table.

Parameters:
  • formulas (list of Formula) – Output of parse_report_formulas().

  • energies (dict[int, EnergyResult]) – Map config_index EnergyResult for every config the formulas reference. Indices come from the configN/ dir name, 1-based. Configs that didn’t converge MUST still be present with EnergyResult.converged = False so this function can attribute the failure correctly.

  • spin (float, optional) – Magnetic spin quantum number S. When given (>0), each JS² is divided by to also report J in meV and in Kelvin.

  • config_n_atoms (dict[int, int], optional) – Map config_index n_atoms from parse_report_config_cells(). When supplied, each contributing config’s total energy is rescaled by N_LCM / N_X before applying the four-state formula — necessary when mag4-magnetic wrote each config in its own primitive cell (--reduce-cell, the default). When omitted every config is assumed to sit in the same cell (legacy behaviour, no rescaling).

  • n_supercell_atoms (int | None)

Returns:

One entry per input formula, same order.

Return type:

list of JResult

mag4.read_all_energies(compound_dir, code, max_index=None, scf_basename=None)[source]

Walk <compound_dir>/configN/ and run the energy parser on each.

Parameters:
  • compound_dir (str) – Root directory containing the configN/ subdirectories.

  • code ({'vasp', 'wien2k'}) – Forwarded to mag4.energy.get_energy().

  • max_index (int, optional) – If given, only configs config1configN are read. Useful to short-circuit on very large compound dirs.

  • scf_basename (str | None)

Returns:

Map config_index EnergyResult. Empty if no configN/ directory was found.

Return type:

dict[int, EnergyResult]

mag4.write_j_values_dat(results, path, compound_dir, code, spin=None, supercell=None, energies=None, config_sz=None, mags=None)[source]

Persist the four-state results to a fixed-width text file.

Beyond the J table this records the supercell used and a per-configuration block (total energy, SCF iterations, converged moment vs the configuration’s Sz) so the file is self-contained — everything shown on screen, plus the moment/Sz consistency check.

# mag4-extract — four-state J values # compound: <dir> code: vasp spin: 3.5 # supercell: 1x1x2 # # Per-configuration energies # config iter energy (eV) Sz mag exp|mag| status # … # # name d_mean(A) JS2(eV) JS2(meV) J(meV) J(K) status J1 3.8987 -0.12345678 -123.456789 -10.082 -116.99 OK

Return type:

None

Parameters:

Modules

analysis

Geometric / post-DFT analyses (bridging-angle scans, WIEN2k moment extraction).

cli

Command-line entry points exposed via [project.scripts].

constants

Physical constants and shared data structures.

dimers

Dimer search for the four-state method (direct intra-cell Cartesian distances).

energy

DFT total-energy parsers (VASP OUTCAR, WIEN2k case.scf).

energy_mapping

Energy-mapping (linear least-squares) configuration generator.

extract

J-value extraction from a completed four-state DFT campaign.

fit

Linear least-squares (energy-mapping) estimator for Heisenberg couplings.

geometry

Geometric fingerprints for J-shell discrimination via M–X–M bond angles.

io_cif

CIF reading: CrystalData construction, M–M pair extraction, distance shells.

io_poscar

VASP POSCAR I/O — read into CrystalData and write supercell geometry.

lattice

Lattice geometry helpers (matrix construction, fractional ↔ Cartesian, PBC distances).

magnon

Multi-sublattice linear spin-wave theory (LSWT) and Tyablikov RPA.

pipeline

High-level CIF analysis pipeline (loads structure, prints distance tables, returns crystal + couplings).

reports

Pretty-printers for distance tables, J-shell summaries and four-state reports.

ring16

16-state method for the four-spin ring (cyclic) exchange Jring.

spinconfig

Generate, group and merge the four spin configurations of the four-state method.

supercell

Build (na × nb × nc) supercells from a CrystalData.

symmetry

Site-permutation symmetry operations on the magnetic sublattice.

vasp

VASP input generation: per-config POSCAR / INCAR / KPOINTS for the four-state pipeline.

wien2k

WIEN2k input generation: configN.inst + configN.struct per four-state spin configuration.