API reference

This page is auto-generated by sphinx.ext.autodoc from the docstrings of the mag4 package.

mag4

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

Constants and data structures

Physical constants and shared data structures.

mag4.constants.KB_MEV: float = 0.08617333262

Boltzmann constant in meV / K.

mag4.constants.DIST_EQUIV_TOL: float = 0.01

Default tolerance (Å) for merging equivalent M–M distances into the same J shell.

class mag4.constants.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.constants.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.constants.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.constants.next_config_idx()[source]

Monotonically increasing integer used to tag FourStateConfig.

Return type:

int

mag4.constants.reset_config_counter()[source]

Reset the global counter — useful when running several pipelines in one process.

Return type:

None

Geometry

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

mag4.lattice.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.lattice.frac_to_cart(frac, lat_mat)[source]

Convert fractional → Cartesian coordinates.

Return type:

ndarray

Parameters:
mag4.lattice.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.lattice.pbc_distance(c1, c2, lat_vecs, inv_lat)[source]

Minimum-image distance between two Cartesian points.

Return type:

float

Parameters:
mag4.lattice.find_site(new_cart, cart_coords, lat_vecs, inv_lat, tol=0.05)[source]

Index of the site in cart_coords closest to new_cart under PBC, or -1.

Return type:

int

Parameters:

Reading / writing

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

mag4.io_cif.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.io_cif.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.io_cif.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.io_cif.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:

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

Layer: VASP-specific (POSCAR format; also the geometry source for the WIEN2k path).

mag4.io_poscar.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.io_poscar.write_poscar(crystal, name)[source]

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

Return type:

str

Parameters:

Supercell

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

mag4.supercell.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.supercell.shortest_periodic_image(lat_mat, max_n=3)[source]

Shortest non-zero supercell lattice vector length.

Iterates over (n_a, n_b, n_c) [-max_n, max_n]^3 \ {(0,0,0)} and returns the minimum |n_a a + n_b b + n_c c|. For cubic / orthorhombic / hexagonal cells this reduces to min(|a|,|b|,|c|); for general triclinic cells the enumeration catches the short diagonal combinations.

Parameters:
  • lat_mat (ndarray) – 3×3 matrix whose columns are the Cartesian lattice vectors a, b, c (mag4’s CrystalData.lattice_matrix convention).

  • max_n (int) – Range of integer coefficients to try. 3 is enough for all standard Bravais types — only severely skewed triclinic lattices could need more, and mag4 doesn’t build those.

Return type:

float

mag4.supercell.find_dimer_direction(conv_lat_mat, target_species, distance, *, dist_tol=0.01, max_image=1)[source]

Return a representative unit vector along a J-shell dimer in the conventional cell.

For each pair (i, j) of magnetic atoms in target_species and each periodic-image translation T within [-max_image, max_image]^3 of the conv lattice, returns the first (r_j + T r_i)/|.| whose length matches distance within dist_tol. None if no such pair exists in the conv cell (rare: would mean the J shell only appears across multiple conv-cell copies in the supercell).

Return type:

Optional[ndarray]

Parameters:
mag4.supercell.find_all_dimer_directions(conv_lat_mat, target_species, distance, *, dist_tol=0.01, max_image=1)[source]

All distinct unit bond directions of a J shell in the conventional cell.

Like find_dimer_direction() but returns every symmetry-equivalent direction (deduplicated by sign, since ±u give the same periodic-image distance). A single coupling can run along several directions at once — e.g. a honeycomb J1 has three — and in an anisotropic supercell they isolate differently, so the isolation check must test all of them, not just one representative. Returns [] if the shell has no in-cell pair.

Return type:

List[ndarray]

Parameters:
mag4.supercell.check_four_state_isolation(conv_lat_mat, multipliers, couplings, target_species=None, *, dimer_vectors=None, rel_tol=0.001, max_multiplicity=None, dimer_multiplicity=None)[source]

Per-J algebraic isolation check for the four-state method.

A Jk dimer (i, j) is isolable in the chosen supercell when its four-state combination E_uu + E_dd E_ud E_du returns only Jk. Flipping (i, j) touches one bond for every periodic image of j that i couples to (all other atoms are a fixed bath that cancels), so the combination equals 4·(Σ over those bonds of their J). Therefore:

  • clean — none of those images sits at a different coupling distance Jm (m≠k). If one does, the formula mixes Jk with Jm and the dimer is contaminated (cross lists the offending Jm). Cleanliness is the whole criterion — this is the “check the cancellation of the other dimers in the 4-state equation” rule.

  • multiplicity M — the number of images at d_k itself ( 1, the bond included). When a compact cell wraps the bond onto its own image through the same Jk (e.g. an axial bond along a doubled axis links ±a, M = 2), the combination yields M·Jk·S² and the extractor divides by the self-coefficient 4·M instead of the textbook 4.

This is purely a property of which images fall at which coupling distance, so it is frame-independent and handles oblique cells. It replaces the older geometric surrogates — bounding box Σ|u_i|L_i, per-axis n_i·L_i 3·d, and the “3 copies of d along the bond” chain rule — all of which demanded a fully isolated (M = 1) dimer and so over-sized the cell. The algebraic rule accepts the smallest cell in which the cross terms cancel, multiplicity and all.

Worked examples (--cutoff giving the J set): tetragonal La₂CuO₄ (J1∥a,b; J2∥[110]) → 2×2×1 (J1 M=2 → /8, J2 M=4 → /16); CsVI₃ (J1, J2 both ∥c) → 1×1×2 (J2 M=2), J1 alone → 1×1×1 (M=2); SrFeO₂ (J1,J2,J3) → 2×2×3 (J2,J3 M=2).

The bond directions come from target_species (all symmetry-equivalent directions) or a single dimer_vectors entry; with neither, a shell has no testable direction and is passed optimistically (M=1).

max_multiplicity (opt-in) caps the accepted dimer multiplicity: a shell is accepted only when it is clean and its multiplicity M (the number of Jk images linking the dimer atoms — the 4·M divisor) does not exceed the cap. max_multiplicity=1 demands a fully isolated dimer (textbook divisor 4) — the geometric-style behaviour exposed on the CLI as --full-isolation — and grows the cell (La₂CuO₄ 2×2×1 → 3×3×1, CsVI₃ 1×1×2 → 1×1×3, SrFeO₂ 2×2×3 → 3×3×3) in exchange for larger, less noise-sensitive cells; intermediate caps (e.g. 2) trade cell size against the largest divisor. A clean shell whose M exceeds the cap is reported ok=False with an empty cross (the failure is high multiplicity, not contamination).

dimer_multiplicity (post-selection only) maps each coupling to the multiplicity of the actually selected dimer — the exact 4·M the report divides by, counted from that dimer’s supercell images. When given it overrides the direction-based worst case for clean shells, so the displayed multiplicity and the cap decision match the formula exactly. The supercell search omits it (no dimer is chosen yet) and stays pessimistic.

Returns a dict with keys:

  • axes — tuple (L_a, L_b, L_c) of supercell axis lengths.

  • d_max — longest coupling distance (the in-range threshold).

  • per_shell — list of {name, d, dimer_dir, multiplicity, cross, ok} dicts, one per coupling. dimer_dir is the chosen clean direction (the one of smallest multiplicity), cross the sorted list of contaminating Jm (empty when clean), ok = (cross is empty). A shell with several symmetry-equivalent directions is OK if any one is clean (mag4 selects that representative dimer).

  • okTrue iff every shell is clean.

  • failing(name, d) tuples for contaminated shells.

  • recommended — smallest (na, nb, nc) (≥ multipliers, by atom count) for which every shell is clean.

Return type:

dict

Parameters:
mag4.supercell.find_optimal_supercell(crystal, couplings, tol, max_mult=4, *, ligand_elements=None, bond_cutoff=2.5, angle_tol=1.0, ll_cutoff=0.0, signed_dihedral=False, isolation=True, max_multiplicity=None)[source]

Return the smallest (na, nb, nc) supercell that resolves and isolates every coupling for the four-state method.

Candidates are enumerated over [1..max_mult]^3 and ranked by (volume, max(na,nb,nc), (na,nb,nc)) so that (1,1,1) is tried first and the most compact / isotropic cells win ties.

Two acceptance criteria are applied to each candidate, in order of size:

  1. Resolve — every coupling has a distinct intra-cell dimer pair (can_assign_unique_dimers()).

  2. Isolate (when isolation=True, the default) — every J shell is also four-state-clean (check_four_state_isolation()): no periodic image of the partner couples to it through a different Jm, so the four-state combination returns only Jk (mixed in with its own multiplicity M, which the extractor divides out as 4·M). This is the algebraic cancellation rule — much weaker than demanding a fully isolated dimer — so the chosen cell is the smallest in which the cross terms vanish.

The smallest candidate passing both is returned. If none isolates within max_mult the function falls back to the smallest resolving cell and logs a warning; the CLI’s post-selection isolation check then errors (the four-state method is only valid for an isolated dimer), telling the user to raise --max-supercell or lower --cutoff. Pass isolation=False to restore the legacy resolve-only selection, or max_multiplicity=N to cap the accepted dimer multiplicity — 1 demands a fully isolated dimer (divisor 4, the old geometric “3 copies of d” rule), larger values trade cell size against the largest 4·M divisor.

When ligand_elements is supplied the geometric fingerprint of each candidate pair must also match the target — sub-shells like J1a / J1b are then resolved as distinct couplings, which may require a larger supercell than distance-only mode.

Raises:

RuntimeError – When no candidate up to max_mult in each direction yields a complete, conflict-free dimer assignment. The message lists the couplings that remained unresolvable.

Return type:

Tuple[Tuple[int, int, int], CrystalData]

Parameters:
mag4.supercell.find_supercell_for_enumeration(crystal, couplings, max_mult=4, *, tol=0.001)[source]

Smallest supercell with no coupling folding onto a periodic self-image.

This is the energy-mapping sizing criterion (distinct from the four-state find_optimal_supercell()). The all-orders enumeration needs every coupling distance d to stay strictly below the shortest lattice translation; otherwise s_i·s_j collapses to s_i² = +1 in every order, the coefficient becomes a constant absorbed into E0, and the coupling is indeterminate in the fit.

Returns the smallest (na, nb, nc) (size-ordered) for which shortest_periodic_image(super) > d_max + tol. If none within max_mult qualifies, returns the largest candidate (which has the longest lattice translations, i.e. the fewest folds) and warns.

Return type:

Tuple[Tuple[int, int, int], CrystalData]

Parameters:

Four-state method

Site-permutation symmetry operations on the magnetic sublattice.

mag4.symmetry.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, ...]]

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

mag4.dimers.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.dimers.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.dimers.can_assign_unique_dimers(dimers, couplings)[source]

Return True iff the greedy assignment used by the four-state pipeline can give every coupling a distinct (idx_a, idx_b) pair.

Mirrors the assignment logic in mag4.cli.fourstate so the optimal supercell search rejects cells that find the right distances but cannot assign them all to non-overlapping site pairs.

Return type:

bool

Parameters:

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

mag4.spinconfig.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.spinconfig.parse_magmom_tokens(s)[source]

Expand a VASP MAGMOM-style string into a flat list of floats.

Accepts plain values and n*v repetitions, whitespace- or comma-separated: "32*0 1 -1 2*1.5"[0.0]*32 + [1.0, -1.0, 1.5, 1.5].

Return type:

List[float]

Parameters:

s (str)

mag4.spinconfig.build_reference_from_magmom(magmom, crystal)[source]

±1 reference bath from a MAGMOM-style string of the WORKING SUPERCELL.

The string is written exactly as one would set VASP’s MAGMOM for the supercell — POSCAR atom order (crystal.all_species species blocks), n*v repetitions allowed — e.g. for a 2×2×1 La₂CuO₄ √2 cell:

32*0  1 1 1 -1 -1 -1 -1 1  -1 -1 -1 1 1 1 1 -1  64*0

Only the SIGNS of the magnetic entries are used (any magnitude, e.g. ±1 or ±0.6); non-magnetic entries must be 0 and are validated against the species layout so an atom-order mismatch fails loudly. As a shorthand the string may instead carry just the N_mag magnetic values (in the magnetic-sublattice order). Returns the ±1 bath in crystal.target_species order.

Return type:

list

Parameters:

magmom (str)

mag4.spinconfig.parse_ref_couplings(spec, coupling_names)[source]

Parse "J1:AFM,J2:FM"{"J1": -1, "J2": +1}.

Raises ValueError on unknown coupling names or unrecognised signs.

Return type:

Dict[str, int]

Parameters:
mag4.spinconfig.build_reference_from_couplings(atoms, lat_mat, couplings, spec, tol)[source]

Build a reference bath that respects the requested coupling signs.

Parameters:
  • atoms (magnetic atoms in legacy [label, x, y, z, …] format.)

  • lat_mat (3×3 lattice matrix (columns are lattice vectors).)

  • couplings ([[name, distance], …].)

  • spec (e.g. "J1:AFM,J2:FM".)

  • tol (distance tolerance for the bond search.)

  • ``(ref_bath (Returns) –

  • used (report)``. report carries the method)

  • any

  • conflict (frustration)

  • ``(satisfied (and per-coupling) –

  • counts. (total)`` bond)

Return type:

Tuple[list, dict]

mag4.spinconfig.parse_kvector(spec)[source]

Parse "1/2 1/2 0" or "0.5,0.5,0"[Fraction(1,2), …].

Return type:

List[Fraction]

Parameters:

spec (str)

mag4.spinconfig.build_reference_from_kvector(atoms, kfrac, supercell, node_tol=1e-06)[source]

Build a collinear reference bath s_i = sign(cos(2π k·R_i)).

Parameters:
  • atoms (magnetic atoms in legacy [label, x, y, z, …] format, with) – fractional coordinates in the working (super)cell.

  • kfrac (propagation vector (3 Fraction) in parent reciprocal-lattice units.)

  • supercell ((na, nb, nc) multipliers used to build the working cell.)

  • ``(ref_bath (Returns) –

  • report)``.

  • node_tol (float)

Return type:

Tuple[list, dict]

mag4.spinconfig.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.spinconfig.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.spinconfig.time_reversal_pairs(unitary_groups, sym_ops)[source]

Pairs of unitary-only classes that time reversal proves degenerate.

unitary_groups must come from find_unique_configs() with time_reversal=False. Two classes pair up when an ANTIUNITARY operation P×T (unitary op composed with global spin flip) maps one representative onto the other — their DFT energies must then coincide even though no unitary operation relates them (they typically differ in total Sz). Returns 1-based (uid_a, uid_b) index pairs; since the unitary subgroup has index 2 in the grey group, each class belongs to at most one pair.

Return type:

List[Tuple[int, int]]

Parameters:

VASP input generation

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

Layer: VASP-specific (one of the two DFT back-ends; see also wien2k.py).

mag4.vasp.build_magmom_lines(unique_groups, crystal, spin_magnitude)[source]

Return one MAGMOM string per unique configuration.

Atom order matches the POSCAR (crystal.all_species order). Magnetic atoms get ±spin_magnitude; all others get 0.

Return type:

List[str]

Parameters:
mag4.vasp.FUNCTIONAL_CHOICES = ('PBE', 'PBE+U', 'PBE0', 'R2SCAN', 'METAGGA')

Recognised values for the functional argument of incar_content().

  • PBE — plain GGA, no on-site Hubbard term, no exact exchange.

  • PBE+U — Dudarev/Liechtenstein DFT+U on the magnetic element only.

  • PBE0 — unscreened hybrid, AEXX=0.25 by default.

  • R2SCAN — convenience alias forcing METAGGA=R2SCAN.

  • METAGGA — meta-GGA family; --metagga selects the flavour (R2SCAN / SCAN / RSCAN / …).

mag4.vasp.XC_CHOICES = ('PBE', 'PBE+U', 'PBE0', 'R2SCAN', 'METAGGA')

Backward-compatible alias (the historic flag was --xc).

mag4.vasp.incar_content(magmom, cfg_label, *, functional=None, xc=None, elements=None, magnetic_element=None, ldaul=None, ldauu=None, ldauj=0.0, aexx=0.25, encut=500.0, metagga='R2SCAN', lmaxtau='auto', ldau_l=None, ldau_u=None, ldau_j=None)[source]

Return the INCAR file content as a string for a given config label.

Parameters:
  • magmom (str) – Pre-built MAGMOM string for this configuration.

  • cfg_label (str) – Human-readable tag printed in SYSTEM.

  • functional (Optional[str]) – Exchange-correlation choice. One of FUNCTIONAL_CHOICES (PBE, PBE+U, PBE0, R2SCAN, METAGGA). Case-insensitive.

  • xc (Optional[str]) – Backward-compatible alias for functional (the historic flag was --xc). functional takes precedence when both are supplied.

  • elements (Optional[List[str]]) – POSCAR element order — needed when functional == "PBE+U" so the LDAU arrays carry one slot per element, and used by the meta-GGA LMAXTAU heuristic.

  • magnetic_element (Optional[str]) – Symbol of the magnetic species (Cu, Ni, Gd, …). The LDAU values apply only to this element; every other element gets -1 / 0.0 / 0.0.

  • ldaul (Optional[int]) – Hubbard parameters for the magnetic element. ldaul is the orbital quantum number (2 for d, 3 for f); ldauu and ldauj are in eV. Required when functional == "PBE+U".

  • ldauu (Optional[float]) – Hubbard parameters for the magnetic element. ldaul is the orbital quantum number (2 for d, 3 for f); ldauu and ldauj are in eV. Required when functional == "PBE+U".

  • ldauj (float) – Hubbard parameters for the magnetic element. ldaul is the orbital quantum number (2 for d, 3 for f); ldauu and ldauj are in eV. Required when functional == "PBE+U".

  • aexx (float) – Fraction of exact exchange in the PBE0 hybrid (default 0.25). Ignored for non-hybrid functionals.

  • encut (float) – Plane-wave cut-off in eV written to ENCUT (default 500).

  • metagga (str) – Meta-GGA flavour written to the METAGGA tag when functional == "METAGGA" (default R2SCAN, but typical choices include SCAN, RSCAN, R2SCAN, R2SCANL). When functional == "R2SCAN" the value is forced to R2SCAN.

  • lmaxtau (Union[int, str]) – Meta-GGA LMAXTAU tag (s→2, p→4, d→6, f→8). "auto" (default): 8 if a lanthanide/actinide is present, else VASP’s default 6 is used (line commented out). Force 6 explicitly for f-in-core POTCARs (RE_3, La_s) where the 4f is not in the valence.

  • ldau_l (Optional[int]) – Deprecated hyphen-style aliases for ldaul/ldauu/ldauj — kept so older callers (and tests) keep working.

  • ldau_u (Optional[float]) – Deprecated hyphen-style aliases for ldaul/ldauu/ldauj — kept so older callers (and tests) keep working.

  • ldau_j (Optional[float]) – Deprecated hyphen-style aliases for ldaul/ldauu/ldauj — kept so older callers (and tests) keep working.

Return type:

str

mag4.vasp.write_incar(magmom, name, total_atoms, **kwargs)[source]

Write an INCAR for Config #1. Returns the file path.

Extra keyword arguments are forwarded to incar_content() (functional / xc, elements, magnetic_element, ldaul/ldauu/ldauj, aexx, encut, metagga, lmaxtau).

Return type:

str

Parameters:
mag4.vasp.DEFAULT_KSPACING = 0.3

Default reciprocal-space k-point spacing (Å⁻¹), VASP-style. Denser than VASP’s own 0.5 Å⁻¹ default — well converged for the magnetic insulators / semiconductors this is usually applied to. METALS need a finer mesh (smaller KSPACING, ~0.15–0.2 Å⁻¹, with appropriate smearing): pass –kspacing explicitly for those.

mag4.vasp.resolve_kmesh(lat_mat, *, kspacing=None, kdens=None)[source]

Resolve the Γ-mesh and how it was chosen.

kspacing (VASP-style, Å⁻¹) takes precedence; else kdens (legacy, Å); else DEFAULT_KSPACING. Returns (mesh, mode, value) with mode in {"kspacing", "kdens"}.

Return type:

Tuple[Tuple[int, int, int], str, float]

Parameters:
mag4.vasp.write_kpoints(lat_mat, path, *, kspacing=None, kdens=None)[source]

Write a Γ-centred automatic KPOINTS file.

The mesh follows VASP’s KSPACING rule by default (denser than VASP’s 0.5 Å⁻¹); pass kdens to use the legacy real-space density instead.

Return type:

str

Parameters:
mag4.vasp.create_config_dirs(unique_groups, crystal, magmom_lines, name, kdens=None, compound='', write_vasp_extras=True, *, kspacing=None, functional=None, xc=None, magnetic_element=None, ldaul=None, ldauu=None, ldauj=0.0, aexx=0.25, encut=500.0, metagga='R2SCAN', lmaxtau='auto', ldau_l=None, ldau_u=None, ldau_j=None)[source]

Create one directory per unique configuration with POSCAR (+ optional INCAR + KPOINTS).

Layout:

<compound>/config1/POSCAR
<compound>/config1/INCAR        (only if write_vasp_extras)
<compound>/config1/KPOINTS      (only if write_vasp_extras)
<compound>/config2/...
Parameters:
  • write_vasp_extras (bool, default True) – When False, only POSCAR is written (useful for the WIEN2k workflow, where POSCAR is later converted to case.struct via WIEN2k’s xyz2struct tool, and the magnetic information lives in case.inst instead).

  • unique_groups (List[List[FourStateConfig]])

  • crystal (CrystalData)

  • magmom_lines (List[str])

  • name (str)

  • kdens (float | None)

  • compound (str)

  • kspacing (float | None)

  • functional (str | None)

  • xc (str | None)

  • magnetic_element (str | None)

  • ldaul (int | None)

  • ldauu (float | None)

  • ldauj (float)

  • aexx (float)

  • encut (float)

  • metagga (str)

  • lmaxtau (int | str)

  • ldau_l (int | None)

  • ldau_u (float | None)

  • ldau_j (float | None)

Return type:

None

WIEN2k input generation

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

Layer: WIEN2k-specific (one of the two DFT back-ends; see also vasp.py).

In each configN/ directory mag4 writes:

  • POSCAR – same VASP file used by --code vasp (kept for cross-checking; also accepted as input by WIEN2k’s xyz2struct).

  • configN.inst – produced by piping the u/d/n spin pattern into WIEN2k’s instgen -ask script. Named after the directory so the case == basename(pwd) convention works out of the box.

  • configN.struct – written directly by Python (no xyz2struct needed). Every atom is emitted as inequivalent (MULT=1) with a unique label of the form <element><n> ("Cs1", "V 9", "I17" …) so WIEN2k cannot merge atoms via symmetry analysis. Only the identity symmetry op is written. This guarantees the atom order in configN.struct matches the order assumed by configN.inst.

Mapping convention

Each entry of the spin pattern maps as follows:

+1 (or any positive  value)  →  ``u``
-1 (or any negative  value)  →  ``d``
 0 (non-magnetic atom)       →  ``n``

So a MAGMOM line -7 2*7 -7 3*7 4*0 becomes d u u d u u u n n n n.

mag4.wien2k.ANG_TO_BOHR: float = 1.8897261339212517

Ångström → Bohr conversion factor used by WIEN2k struct files.

mag4.wien2k.SYMMORPHIC_SG_NUMBERS: frozenset = frozenset({1, 2, 3, 5, 6, 8, 10, 12, 16, 21, 22, 23, 25, 35, 38, 42, 44, 47, 65, 69, 71, 75, 79, 81, 82, 83, 87, 89, 97, 99, 107, 111, 115, 119, 121, 123, 139, 143, 146, 147, 148, 149, 150, 155, 156, 157, 160, 162, 164, 166, 168, 174, 175, 177, 183, 187, 189, 191, 195, 196, 197, 200, 202, 204, 207, 209, 211, 215, 216, 217, 221, 225, 229})

The 73 symmorphic space groups (no glide planes / no screw axes). Used by the --no-reduce branch in write_symmetric_case_files() to decide whether to refine orbit groupings (_cell_compatible_symmetry + _relabel_…) or to fall back to “every atom MULT=1” (_ungrouped_symmetry).

For symmorphic parents, the refined point group at origin is closed under composition modulo the supercell lattice, so the union-find in _cell_compatible_symmetry produces correct supercell-as-P-Bravais orbits. For non-symmorphic parents the parent’s intrinsic glide / screw translations alias with supercell-induced internal translations and the union-find over-merges (e.g. MULT=16 in a Pmna 2×2×1 supercell where Pmna’s max Wyckoff multiplicity is 8).

mag4.wien2k.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.wien2k.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.wien2k.wien2k_label(element_symbol, idx)[source]

Format an atom label per WIEN2k case.struct convention.

  • Two-letter element: e.g. "Cs1" → cols 1-2 = "Cs".

  • One-letter element: "V 1" (letter + space) → cols 1-2 = "V ".

The convention matters because instgen reads the element from the first two characters of each RMT= line in case.struct.

Return type:

str

Parameters:
  • element_symbol (str)

  • idx (int)

mag4.wien2k.write_stub_struct(crystal, path)[source]

Write a minimal case.struct good enough for instgen.

Only the per-atom RMT= line is emitted (one per atom in POSCAR order) because that is the only thing instgen parses. Use write_struct_file() to produce a full, ready-to-use struct file.

Return type:

None

Parameters:
class mag4.wien2k.ConfigSymmetry(spacegroup_number, spacegroup_symbol, rotations, translations, origin_shift, new_to_old, orbits, frac_coords_new, mapping_to_primitive=<factory>)[source]

Bases: object

Symmetry analysis of a single spin-decorated configuration.

The cell is kept verbatim from the input CrystalData; only the atom ordering (within the cell) and the symmetry-op list are derived.

Parameters:
spacegroup_number, spacegroup_symbol

Space group reported by spglib for the spin-decorated structure.

rotations, translations

Symmetry operations in the input cell basis, ready to be written verbatim into a WIEN2k struct file (rotations is integer; the translations are in fractional coordinates).

origin_shift

Origin shift that spglib would apply to put the structure into the standard setting. Kept for diagnostics; not applied to atom positions here — we keep input positions so the rotations/translations in the input basis act directly.

new_to_old

new_to_old[i] is the original POSCAR index of the atom that now sits at position i in the symmetrised order. Length = number of atoms.

orbits

List of Wyckoff orbits in the new order: each orbit is a list of consecutive new-order indices that share a symmetry equivalent class. len(orbits) is the number of inequivalent sites (== WIEN2k’s NEQUIV).

frac_coords_new

Fractional coordinates in the new order (shape (n_atoms, 3)).

spacegroup_number: int
spacegroup_symbol: str
rotations: ndarray
translations: ndarray
origin_shift: ndarray
new_to_old: List[int]
orbits: List[List[int]]
frac_coords_new: ndarray
mapping_to_primitive: List[int]

For each atom in the new order, the index of the corresponding atom in spglib’s primitive cell. Used by the WIEN2k struct writer to fold orbits by lattice-centering equivalence (only one rep per primitive index is emitted under an F/B/C-centred letter, matching WIEN2k’s “list one of four / one of two” convention).

property n_atoms: int
property n_inequiv: int
class mag4.wien2k.MagneticGroupInfo(uni_number, bns_number, og_number, msg_type, parent_number, parent_symbol, n_unitary, n_primed, mpg_symbol='', mpg_kind='')[source]

Bases: object

Magnetic (Shubnikov) space group of one spin-decorated configuration.

spglib classifies the configuration (collinear moments included, so the PRIMED operations — unitary op × time reversal — are counted) into one of the 1651 magnetic space-group types. spglib provides the standard numbers but not the symbol string; the BNS number is the citable label (symbol lookup: Bilbao MGENPOS / ISO-MAG).

msg_type is the Shubnikov type: 1 = colorless (no primed ops), 2 = grey (contains pure time reversal — only for zero-moment structures), 3 = black-white (half the point operations primed), 4 = black-white with anti-translations (a translation × T maps the sublattices).

Parameters:
  • uni_number (int)

  • bns_number (str)

  • og_number (str)

  • msg_type (int)

  • parent_number (int)

  • parent_symbol (str)

  • n_unitary (int)

  • n_primed (int)

  • mpg_symbol (str)

  • mpg_kind (str)

uni_number: int
bns_number: str
og_number: str
msg_type: int
parent_number: int
parent_symbol: str
n_unitary: int
n_primed: int
mpg_symbol: str = ''
mpg_kind: str = ''
property type_roman: str
short()[source]

Compact display string, e.g. BNS 65.490 (type IV, parent Cmmm).

Return type:

str

point_group()[source]

Magnetic point group with its kind, e.g. mmm1' (grey).

Dropping the translations from the magnetic space group gives the magnetic point group exactly: an anti-translation (type IV) becomes pure time reversal 1’, so the point group is GREY G1'; a type-III group keeps half its point operations primed and is written in the Shubnikov G(H) notation (parent, unitary subgroup); a type-I group is the ordinary (colourless) symbol.

Return type:

str

mag4.wien2k.analyze_config_magnetic_group(crystal, group, *, spin_magnitude=1.0, symprec=0.001)[source]

Magnetic space group (Shubnikov/BNS) of a spin-decorated configuration.

Unlike analyze_config_symmetry() — which colours up/down spins as different species and therefore sees only the UNITARY part of the magnetic group — this passes the collinear moments to spglib’s magnetic dataset, so operations combined with time reversal (primed) are included. Returns None when spglib is too old (< 2.0) or fails; callers should then simply omit the magnetic-group line.

Return type:

Optional[MagneticGroupInfo]

Parameters:
mag4.wien2k.analyze_config_symmetry(crystal, group, *, spin_magnitude=1.0, symprec=0.001)[source]

Run spglib on the spin-decorated configuration and pack the result.

Atoms in CrystalData are coloured according to their spin (see _spglib_atom_types()), so the detected space group is the magnetic space group of the configuration projected onto the user’s cell — equivalent to “highest symmetry compatible with this cell”.

The returned ConfigSymmetry carries:

  • the rotations/translations in the input cell basis (so we can write them straight into a WIEN2k struct file);

  • the permutation that groups atoms by their Wyckoff orbit, with orbits ordered by the index of their representative — this becomes the new atom order on disk.

On any spglib error or empty dataset we fall back to a P1 (identity-only) ConfigSymmetry so the rest of the pipeline can proceed unchanged.

Return type:

ConfigSymmetry

Parameters:
mag4.wien2k.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.

mag4.wien2k.write_struct_file_symmetric(crystal, config_sym, path, *, title=None, rmt=2.0, npt=781, r0=None, calc_mode='RELA', lattice=None, labels=None)[source]

Write a WIEN2k case.struct that exposes the configuration’s detected space-group symmetry.

One inequivalent block is emitted per Wyckoff orbit (MULT = orbit size), with the orbit’s representative listed on the leading ATOM line and the remaining members on continuation lines between the MULT= line and the per-orbit NPT/R0/RMT line. Each orbit gets a unique <element><n> label so WIEN2k cannot merge orbits that happen to share an element. The trailing symmetry block lists every rotation/translation reported by spglib in the input cell basis.

Atom positions are written in the order encoded by config_sym.frac_coords_new — i.e. spglib’s equivalence classes grouped together, with orbits ordered by their representative index.

Return type:

None

Parameters:
mag4.wien2k.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.wien2k.write_symmetric_case_files(unique_groups, crystal, parent_dir, *, instgen_path=None, spin_magnitude=1.0, symprec=0.001, rmt=2.0, npt=781, r0=None, reduce_cell=False)[source]

Write configN.struct and configN.inst per config with a shared, symmetry-derived atom order.

For each configuration:

  1. Run analyze_config_symmetry() once on the spin-decorated cell.

  2. Write configN.struct via write_struct_file_symmetric() — one block per Wyckoff orbit, full symmetry-op tail, unique per-orbit labels.

  3. Drive instgen with one u/d/n answer per orbit (the spin of the orbit’s representative) and save the resulting configN.inst.

Because both files are produced from the same ConfigSymmetry, their atom orders are guaranteed to agree without any post hoc reordering.

Returns a dict with four keys: "struct" (list of struct paths), "inst" (list of inst paths), "sym" (list of ConfigSymmetry results, one per config — handy for tests), and "crystals" (the per-config reduced CrystalData used to write each struct — used by the report writer to record per-config cell metadata for mag4-extract’s cell-size rescaling).

When reduce_cell is true (--reduce; the default is off, so every config keeps the one working supercell and shares identical numerics) each config is rewritten in its colored-structure primitive cell via mag4.geometry.reduce_to_primitive() — multiplicities then match the parent SG’s standard Wyckoff table, and downstream DFT runs are correspondingly cheaper for high-symmetry configs.

Return type:

Dict[str, List]

Parameters:
mag4.wien2k.write_job_scripts(parent_dir, *, functional=None, kspacing=None, element=None, ldaul=None, ldauu=None, ldauj=None)[source]

Write job.init and job.run into parent_dir and chmod +x them.

Both scripts iterate over the sibling config*/ directories via foreach; they belong at the compound level, not inside each configN/. Existing files are overwritten so re-running mag4-magnetic is safe. The template follows P. Blaha’s recommended workflow: setrmt (so the muffin-tin radii mag4 writes into the struct files are placeholders), init_lapw -sp -prec 2n -noreduc -nohdlo -nokshift (-nokshift is always present, so the k-mesh is unshifted by default), and an interactive check when sgroup proposes an origin-shifted struct (accept only if lattice type and atom count are unchanged — sgroup may e.g. reduce P to CXY, which would silently change the cell the report’s energy formulas refer to).

kspacing (Å⁻¹, VASP convention; defaults to mag4’s 0.3) is converted to bohr⁻¹ and inserted as init_lapw -numk -1 <spacing> so the WIEN2k k-mesh matches the VASP one. When functional == "PBE+U" and ldauu is given, the init_orb -orb heredoc is generated active with <element> <l> <U> <J> from element/ldaul/ldauu/ldauj, and job.run calls runsp with -orb; otherwise the block stays commented out as a template.

ldauu/ldauj are taken in eV (VASP convention — the very same values the INCAR’s LDAUU/LDAUJ receive) and divided by RY_TO_EV on the way out, because init_orb reads U and J in Rydberg. ldaul is a quantum number and is passed through unchanged. A comment above the heredoc echoes both the eV input and the Ry output so the conversion is checkable in the generated script.

Return type:

List[str]

Parameters:
  • parent_dir (str)

  • functional (str | None)

  • kspacing (float | None)

  • element (str | None)

  • ldaul (int | None)

  • ldauu (float | None)

  • ldauj (float | None)

mag4.wien2k.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.wien2k.run_instgen(udn_letters, crystal, instgen_path)[source]

Run instgen -ask once and return the produced case.inst text.

Internally uses a temporary directory containing a stub case.struct, pipes the u/d/n letters as answers, runs instgen, reads back the resulting case.inst and cleans up.

Raises:

RuntimeError – If instgen exits non-zero or fails to produce case.inst.

Return type:

str

Parameters:
mag4.wien2k.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

Energy parsing

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

The get_energy() dispatcher returns an EnergyResult describing both the converged total energy (in eV) and a structured convergence status, which the upcoming mag4-extract CLI uses to decide whether each configuration is safe to feed into the J-extraction formulas.

Only the fields actually needed for the four-state pipeline are extracted:

  • the last total energy printed in the file (assumed converged);

  • a coarse convergence flag (the calculation finished cleanly);

  • for WIEN2k: the last :DIS (charge distance) value and the verbatim :DIS / :ENE lines, so the user can verify the parser picked up the right data and that the SCF converged tightly enough.

Both parsers are streaming (line-by-line) and tolerate large output files without loading them entirely into memory.

mag4.energy.RY_TO_EV: float = 13.605693122994

1 Rydberg in electron-volts (CODATA 2018).

class mag4.energy.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.energy.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.energy.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.energy.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

mag4.energy.read_vasp_magnetization(config_dir)[source]

Total magnetization (μ_B) of a converged VASP run, or None.

Prefers the last mag= in OSZICAR; falls back to the OUTCAR number of electron magnetization line. Collinear (ISPIN=2) runs report a single scalar moment.

Return type:

Optional[float]

Parameters:

config_dir (str)

mag4.energy.read_vasp_site_moments(config_dir, ion_indices)[source]

Per-ion magnetic moments (μ_B) from a VASP OUTCAR.

Parses the last magnetization (x) block (the converged one; needs LORBIT=10/11 in the INCAR — mag4 sets LORBIT=11 by default) and returns the tot column (last field of each ion row) for the requested 1-based ion indices. Ion order follows the POSCAR; mag4 writes the magnetic atoms first, so they are ions 1 n_mag.

Returns {ion: float or None} (None for an ion the block didn’t list, or {}-like all-None when no block/OUTCAR was found).

Return type:

Dict[int, Optional[float]]

Parameters:

config_dir (str)

mag4.energy.read_site_moments(config_dir, code, ion_indices, scf_basename=None)[source]

Per-site magnetic moments for the requested code (dispatcher).

Return type:

Dict[int, Optional[float]]

Parameters:
  • config_dir (str)

  • code (str)

  • scf_basename (str | None)

mag4.energy.read_wien2k_magnetization(config_dir, scf_basename=None)[source]

Total magnetic moment in the cell (μ_B) from the last :MMTOT line.

The SCF file is resolved exactly as in get_energy() (<basename(config_dir)>.scf unless scf_basename overrides it).

Return type:

Optional[float]

Parameters:
  • config_dir (str)

  • scf_basename (str | None)

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

Total magnetization (μ_B) for the requested code, or None.

Return type:

Optional[float]

Parameters:
  • config_dir (str)

  • code (str)

  • scf_basename (str | None)

class mag4.energy.BandGap(gap_eV, metallic, vbm_eV=None, cbm_eV=None, source='', note='')[source]

Bases: object

Band gap of one configuration (eV).

gap_eV is the conduction–valence separation (0.0 when metallic); metallic is True when occupied and empty states overlap (no gap), None when the file carried no usable eigenvalue information.

Parameters:
gap_eV: float | None
metallic: bool | None
vbm_eV: float | None = None
cbm_eV: float | None = None
source: str = ''
note: str = ''
mag4.energy.extract_vasp_gap(outcar_path, *, occ_tol=0.5, gap_tol=0.05)[source]

Band gap from the eigenvalue/occupation block at the end of a VASP OUTCAR.

Reads the last eigenvalue dump (after the final E-fermi line, all k-points and spin channels), splits bands into occupied (occ > occ_tol) and empty, and reports CBM VBM. Metallic when they overlap.

Return type:

BandGap

Parameters:
mag4.energy.extract_wien2k_gap(scf_path, *, occ_tol=0.5, gap_tol=0.05)[source]

Band gap from a WIEN2k SCF file.

Prefers the :GAP line (printed when lapw2 finds a gap). Otherwise — e.g. with temperature smearing — uses the Bandranges / :BANnnnnn block: VBM = highest emax of an occupied band (occ > occ_tol), CBM = lowest emin of an empty band; energies are in Ry and converted to eV.

Return type:

BandGap

Parameters:
mag4.energy.read_band_gap(config_dir, code, scf_basename=None, *, gap_tol=0.05)[source]

Band gap (eV) + metallicity for one config, or BandGap(None, None).

Return type:

BandGap

Parameters:
  • config_dir (str)

  • code (str)

  • scf_basename (str | None)

  • gap_tol (float)

J-value extraction

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

Pipeline (mirrors what the user did manually before this module existed):

  1. Parse the four_state_report.dat written by mag4.cli.fourstate.main() to recover, for each shell Jₙ, which 4 unique configurations contribute to the formula JS² = (E_uu + E_dd E_ud E_du) / 4.

  2. Read each configuration’s converged DFT total energy from configN/ via mag4.energy.get_energy().

  3. Combine energies with the formulas to produce JS² (and J if a spin S is given).

This module is the pure-Python core; the CLI in mag4.cli.extract adds argument parsing, pretty-printing and j_values.dat output.

mag4.extract.DEFAULT_REPORT_NAME = 'four_state_report.dat'

Name of the report file produced by mag4-magnetic (default).

class mag4.extract.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.

mag4.extract.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.extract.parse_report_config_cells(report_path)[source]

Read per-config n_atoms values from a four_state_report.dat.

The report emits a Cell: a=… | | n_atoms=N | line under each Config #uid: block when WIEN2k cell reduction is on. Returns {config_index n_atoms}. Older reports without the line yield an empty dict and the caller falls back to assuming every config sits in the same cell (today’s behaviour, no rescaling).

Return type:

Dict[int, int]

Parameters:

report_path (str)

mag4.extract.read_wien2k_struct_atom_count(struct_path)[source]

Sum the MULT=N fields across a WIEN2k case.struct file.

This is the authoritative atom count WIEN2k’s :ENE refers to. Whenever init_lapw’s sgroup rewrites the struct (cell reduction, lattice-letter correction), mag4’s pre-emption from four_state_report.dat can disagree – the post-sgroup file on disk is the truth. Returns None if the file is missing or no MULT= field could be parsed.

Example: a P6_3/mmc primitive cell with 2 Cr + 2 Sb listed (each MULT=2) returns 4.

Return type:

Optional[int]

Parameters:

struct_path (str)

mag4.extract.read_struct_atom_counts(compound_dir, n_configs)[source]

For every configN/ directory under compound_dir, sum the MULT= fields of configN.struct (whatever WIEN2k’s init_lapw left on disk after possibly accepting sgroup’s rewrite) and return {config_index atom_count}.

Missing struct files or unparseable ones are simply skipped – callers should fall back on the report’s recorded counts.

Return type:

Dict[int, int]

Parameters:
  • compound_dir (str)

  • n_configs (int)

mag4.extract.parse_report_supercell_atoms(report_path)[source]

Read the total atom count in the supercell from a four_state_report.dat header.

The four-state formula is defined on the supercell mag4 built (e.g. NiO 1×1×2 → 16 atoms). When configs are computed in reduced cells (Fm-3m conv → 2 atoms, etc.), their WIEN2k :ENE values must be scaled up to the supercell size before applying the formula. This helper returns the integer that mag4-extract should use as the scaling target.

Returns None for reports written by mag4 versions before the Total atoms in supercell line was added; the caller then falls back to math.lcm(per-config n_atoms).

Return type:

Optional[int]

Parameters:

report_path (str)

mag4.extract.parse_report_supercell(report_path)[source]

Read the supercell multipliers (na, nb, nc) from a four-state report header line Supercell : na×nb×nc. None if absent.

Return type:

Optional[Tuple[int, int, int]]

Parameters:

report_path (str)

mag4.extract.parse_report_config_sz(report_path)[source]

{config_index: Sz} from the per-config Spins: lines, where Sz = Σ s_i of the ±1 spin vector (the net collinear moment quantum number of that configuration).

Return type:

Dict[int, int]

Parameters:

report_path (str)

mag4.extract.parse_report_tr_pairs(report_path)[source]

[(configA, configB), …] from the TIME-REVERSAL DEGENERACY CHECK section written by mag4-magnetic --check-degeneracy (any method).

Each pair is related only by an antiunitary operation (unitary crystal op × time reversal), so the two DFT energies must coincide; the caller reports |ΔE| per pair. Empty when the report has no such section.

Return type:

List[Tuple[int, int]]

Parameters:

report_path (str)

class mag4.extract.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.extract.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.extract.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.extract.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:

Reports and pipeline

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

mag4.reports.print_mm_distances(results, element, cutoff, structure)[source]

Tabular dump of all M–M pairs found within cutoff.

Return type:

None

mag4.reports.print_inequivalent(groups, element, tol, *, ligand_elements=None, bond_cutoff=2.5, angle_tol=1.0, ll_cutoff=0.0, z_formula_units=1)[source]

Tabular dump of the inequivalent J shells.

The Multip. column gives the number of distinct M–M dimers per primitive cell, taking periodic boundary conditions into account (computed via pymatgen’s Structure.get_neighbors() with canonical (min_idx, max_idx, image) deduplication). A second /f.u. column normalises that by z_formula_units (the number of formula units in the primitive cell, taken from the reduced composition).

When ligand_elements is supplied the shells were produced by mag4.geometry.group_inequivalent_geom(); an extra M-L-M bridge column is shown. Without it, the legacy mag4.io_cif.group_inequivalent() output is printed.

Return type:

None

Parameters:
mag4.reports.print_report(all_configs, unique_groups, atoms, couplings, ref_bath, n_sym_ops, supercell, output_file=None, *, per_config_cells=None, n_supercell_atoms=None, multiplicities=None, config_coefficients=None, ring_coefficients=None, ring_edge=None, spectator_shells=None, spectator_coefficients=None, tr_pairs=None, config_msgs=None)[source]

Print (and optionally write) the full four-state analysis report.

per_config_cells (optional) is a list, one dict per unique config in unique_groups order, with keys cell (6-tuple a,b,c,α,β,γ), n_atoms (int), volume_A3 (float), sg_number (int), sg_symbol (str). When supplied, a one-line cell summary is emitted after each Config #N block so mag4-extract can rescale per-config energies for the four-state formula.

Return type:

None

Parameters:

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

mag4.pipeline.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:

Sub-package mag4.magnon

Inequivalent magnetic sites and bond table for the multi-sublattice exchange matrix.

mag4.magnon.sublattice.get_inequivalent_sites(cif_path, element)[source]

Group magnetic sites into crystallographically inequivalent classes.

Uses pymatgen.symmetry.analyzer.SpacegroupAnalyzer to derive Wyckoff orbits and returns one list of global site indices per class.

Return type:

List[List[int]]

Parameters:
  • cif_path (str)

  • element (str)

mag4.magnon.sublattice.get_sublattice_bonds(cif_path, element, cutoff, shells, j_values, tol=0.01)[source]

Build the bond table for the full N_mag × N_mag primitive-cell exchange matrix.

Every magnetic atom is treated as its own sublattice. The reciprocal-space exchange matrix is

\[J_{αβ}(q) = Σ_{\text{images}} J_n · \exp(i\,q · r_{αβ,\text{image}})\]
Returns:

Keys: n_sub, site_labels, bonds, inequiv_groups, global_to_wyck, mag_idx, cif_path, orig_rec_matrix, prim_fracs, lat_matrix.

Return type:

dict

Parameters:

q-point grid generation for the BZ scan.

mag4.magnon.qmesh.build_qmesh(structure, na, nb, nc)[source]

Γ-centred uniform q-mesh over the full primitive BZ.

Returns:

q_cart, q_frac – Cartesian (Å⁻¹) and fractional reciprocal-lattice coordinates, each of shape (na·nb·nc, 3).

Return type:

ndarray

Parameters:
mag4.magnon.qmesh.auto_qmesh(structure, target_points=125000)[source]

Anisotropy-adapted q-mesh targeting ~``target_points`` q-points.

Return type:

Tuple[int, int, int]

Parameters:

Reciprocal-space exchange matrix J(q) and its eigenvalue spectrum.

mag4.magnon.exchange.build_jq_matrices(bond_data, q_points)[source]

Compute \(J_{αβ}(q) = Σ_{\text{bonds}(α,β)} JS²_n · e^{i\,q · r_{αβ}}\).

Returns a complex array of shape (N_q, N_sub, N_sub).

Return type:

ndarray

Parameters:
mag4.magnon.exchange.compute_eigenvalues_all_q(Jq)[source]

Diagonalise the Hermitian J(q) at every q.

Returns a real array (N_q, N_sub) of eigenvalues sorted descending. Used for T_c and q₀ search; the magnon dispersion uses the LSWT dynamical matrix instead (see mag4.magnon.dispersion).

Return type:

ndarray

Parameters:

Jq (ndarray)

Ground-state ordering analysis: Luttinger-Tisza q₀ and spin direction assignment.

mag4.magnon.ordering.analyse_ordering(Jq, evals, q_frac, q_cart, structure)[source]

Find q₀ = argmin λ_min(q) and characterise the magnetic ordering.

The output dict contains:

idx0, q0_cart, q0_frac, lam0, eigvec, ratios, character, sc_dims, commensurate.

Return type:

Dict

mag4.magnon.ordering.get_spin_dirs_from_q0(bond_data, q0_frac)[source]

Assign collinear spin directions in the primitive cell from the ordering wavevector.

\[s_α = \mathrm{sign}\bigl(\cos(2π\,q₀ · r_α)\bigr)\]

where r_α is the fractional coordinate of site α. This is the TB2J strategy: the LSWT dynamical matrix is then built directly in the primitive cell, giving exactly n_sub magnon branches with a Goldstone mode at k = q₀. No supercell replication is required.

Return type:

ndarray

Parameters:

Critical temperature (mean-field and Tyablikov RPA) from J(q).

mag4.magnon.tc.compute_tc(evals, S, lam0)[source]

Mean-field and Luttinger–Tisza Tyablikov RPA critical temperature.

Both estimates use the Hermitian J(q) eigenvalue spectrum sampled on the BZ grid.

Returns a dict with keys Tc_MF, Tc_RPA, note.

Return type:

Dict

mag4.magnon.tc.compute_tc_lswt(bond_data, S, ordering_lt, q_cart, evals_lt)[source]

Method-2 T_c, chosen based on q₀.

Return type:

Dict

Parameters:
q₀ ≈ Γ (FM / type-I AFM)

LSWT is exact in the primitive cell. Build \(D^{ord}\) with \(s_α = \mathrm{sign}(v_α)\) and apply the Tyablikov RPA to the acoustic (lowest) magnon branch:

k_B T_c^{LSWT-RPA} = (S+1)/(3S) / ⟨1/ω_0(q)⟩_{ω_0 > 0}
q₀ ≠ Γ (commensurate or incommensurate ordering)

\(D^{ord}\) in the primitive cell has negative eigenvalues because \(s_α = \mathrm{sign}(\cos 2π q₀·r_α)\) is not the exact collinear ground state for q₀ ≠ Γ. Use the LT acoustic-branch RPA — Tyablikov RPA applied to λ_min(q) directly:

k_B T_c^{LT-ac-RPA} = (S+1)/(3S) / ⟨1/(λ_min(q₀) − λ_min(q))⟩_{q ≠ q₀}

High-symmetry k-path generation (auto via pymatgen, or user-supplied label string).

mag4.magnon.kpath.get_kpath(structure, n_points=100, custom_labels=None)[source]

Generate a high-symmetry k-path using pymatgen HighSymmKpath.

Parameters:
  • structure (pymatgen Structure)

  • n_points (int) – Number of points per segment.

  • custom_labels (str, optional) – Space-separated label string, e.g. M K Γ | A L H". | marks a discontinuity. If None the automatic pymatgen path is used.

Returns:

Keys: kpoints_cart, kpoints_frac, distances, tick_positions, tick_labels, break_indices, prim_rec_matrix, hs_frac.

Return type:

dict

Magnon dispersion via the eigenvalues of J(q) in the primitive cell.

mag4.magnon.dispersion.compute_magnon_dispersion(bond_data, kpath, S, ordering)[source]

Compute the magnon dispersion in the primitive cell.

\[ω_k(q) = 2S × \bigl(λ_k(J(q)) − λ_{\min}(q_0)\bigr)\]

where λ_k(J(q)) are the eigenvalues of the Hermitian exchange matrix at each k-point along the path, sorted ascending. This gives exactly n_sub branches in the primitive BZ for any ordering wavevector q₀. The Goldstone mode (ω = 0) is at k = q₀ by construction of the shift, so no supercell or spectral unfolding is needed.

Return type:

Tuple[ndarray, Dict]

Parameters:

Magnon band-structure plotting (matplotlib, optional).

mag4.magnon.plot.plot_magnon_bands(magnon_bands, kpath, S, n_sub, name)[source]

Plot the magnon band structure and save to <name>_magnon_bands.png.

Returns the output path, or "" if matplotlib is unavailable.

Return type:

str

Parameters:

Magnon / spin-wave report (textual summary written to disk).

mag4.magnon.report.print_report(shells, j_values, bond_data, evals, ordering_lt, tc_lt, tc_sw, S, qmesh, structure, output_file, j_values_note=None)[source]

Pretty-print the LSWT/RPA report and write it to output_file.

j_values_note (optional): provenance lines printed directly under the exchange-parameter table — e.g. the --jring ring-renormalisation record, so effective couplings are never shown without their origin.

Sub-package mag4.analysis

Bridging-ligand bond-angle analysis (M – L – M).

mag4.analysis.bond_angles.get_mm_pairs(structure, element, cutoff)[source]

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

Mirrors mag4.io_cif.get_mm_distances() but returns lighter dicts (no label_* fields) suited to the bond-angle workflow.

Parameters:
mag4.analysis.bond_angles.group_into_shells(results, tol=0.01)[source]

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

Parameters:

tol (float)

mag4.analysis.bond_angles.find_bridging_angles(structure, site_i, site_j, jimage, ligand_elements, bond_cutoff)[source]

Find every ligand L bridging site_i and site_j (at jimage).

A ligand L bridges the pair when dist(site_i, L) < bond_cutoff AND dist(site_j + jimage, L) < bond_cutoff.

Returns a list of dicts sorted by angle.

mag4.analysis.bond_angles.find_dihedral_paths(structure, site_i, site_j, jimage, ligand_elements, bond_cutoff, ll_cutoff)[source]

Find every M1–L1–L2–M2 super-super-exchange path between site_i (home) and site_j (at jimage).

A path exists when:

  • L1 is bonded to M1 (distance ≤ bond_cutoff);

  • L2 is bonded to M2 + jimage (distance ≤ bond_cutoff);

  • L1 and L2 are bonded to each other (distance ≤ ll_cutoff);

  • L1 and L2 are distinct ligand atoms (otherwise the path is a direct M–L–M bridge already covered by find_bridging_angles()).

The returned dihedral is the signed torsion angle of the four-atom path in degrees, in [-180°, +180°] following the IUPAC convention.

Returns a list of dicts, deduplicated on (L1 identity, L2 identity) and sorted by signed dihedral.

mag4.analysis.bond_angles.compress_angles(ang_vals, tol=0.01)[source]

Group similar angles: [159.07, 159.07, 159.07]'159.07° (x3)'.

Return type:

str

Parameters:
mag4.analysis.bond_angles.print_header(structure, element, ligand_elements, cutoff, bond_cutoff)[source]

Print a header summarising the structure and the search parameters.

mag4.analysis.bond_angles.print_shell_summary(groups, element)[source]

Brief summary of the J shells.

mag4.analysis.bond_angles.print_angles_for_shell(group, structure, element, ligand_elements, bond_cutoff, lines_out)[source]

Bond-angle table for one J shell. Appends each printed line to lines_out.

mag4.analysis.bond_angles.write_report(lines_out, output_file)[source]

Write accumulated report lines to output_file.

Extract converged magnetic moments from WIEN2k .scf files.

Layer: WIEN2k-specific (backs the mag4-moments CLI).

mag4.analysis.wien2k.parse_report_target_moment(report_path)[source]

Read the intended per-site |MAGMOM| = 2·S from a mag4 report.

Returns the target moment (μ_B) or None if the report has no MAGMOM (S = …, |MAGMOM| = 2·S = …) header (older/other reports).

Return type:

Optional[float]

Parameters:

report_path (str)

mag4.analysis.wien2k.find_report(base_dir='.')[source]

Locate a *_report.dat in base_dir (prefers four_state_report).

Return type:

Optional[str]

Parameters:

base_dir (str)

mag4.analysis.wien2k.find_cases(base_dir='.', scf_basename=None, code='wien2k')[source]

Auto-detect config subdirectories with results for code (sorted).

code='wien2k' → directories with a .scf file (or exactly <scf_basename>.scf when given); code='vasp' → directories with an OUTCAR.

Return type:

List[str]

Parameters:
  • base_dir (str)

  • scf_basename (str | None)

  • code (str)

mag4.analysis.wien2k.extract_moments_from_scf(scf_path, atom_indices)[source]

Parse a WIEN2k .scf file and return the converged :MMIxxx: values.

Line format:

:MMI009: MAGNETIC MOMENT IN SPHERE  9 =  2.85000

The last occurrence of each tag (i.e. the converged value) is kept.

Returns:

{atom_index: float or None}.

Return type:

dict

Parameters:
mag4.analysis.wien2k.extract_total_moment(scf_path)[source]

Return the last :MMTOT: total magnetic moment in cell.

Return type:

Optional[float]

Parameters:

scf_path (str)

mag4.analysis.wien2k.build_table(cases, atom_indices, scf_basename=None, target=None, tol=0.5, code='wien2k')[source]

Textual table of moments for each case + statistics + a target check.

code selects the parser: 'wien2k' reads per-sphere :MMI: values from <dir>/<scf_basename>.scf; 'vasp' reads the per-ion tot column of the last magnetization (x) block in <dir>/OUTCAR (needs LORBIT=10/11). For VASP the indices are 1-based POSCAR ion numbers (mag4 writes the magnetic atoms first, so they are 1 n_mag).

Returns (lines, summary) where summary carries n_data (configs with a readable SCF), n_missing (SCF not found), failures (list of (case, reason) where a site did not reach the target moment within tol, or collapsed to ~0), and target.

target is the expected per-site |μ| (e.g. 2·S = the generated |MAGMOM|). When None the check only flags collapsed moments (|μ| < 0.1 μ_B), which usually mean the config fell into a non-magnetic solution rather than the intended spin state.

Return type:

Tuple[List[str], dict]

Parameters:

Sub-package mag4.cli

CLI: full four-state pipeline (CIF or POSCAR → spin configurations + VASP/WIEN2k inputs).

mag4.cli.fourstate.parse_couplings(s)[source]

Parse "J1:3.94,J2:6.10"[["J1", 3.94], ["J2", 6.10]].

Return type:

list

Parameters:

s (str)

mag4.cli.fourstate.parse_ref_state(s)[source]

Parse "1,-1,1"[1, -1, 1] with ±1 validation.

Return type:

List[int]

Parameters:

s (str)

mag4.cli.fourstate.build_parser()[source]
Return type:

ArgumentParser

mag4.cli.fourstate.main(argv=None)[source]
Return type:

int

CLI: extract J values from a completed four-state DFT campaign.

Usage example:

cd MyCompound/                                # (contains configN/ + four_state_report.dat)
mag4-extract --code vasp --spin 3.5
# → prints a table; also writes MyCompound/j_values.dat
mag4.cli.extract.build_parser()[source]
Return type:

ArgumentParser

mag4.cli.extract.print_j_table(results, spin=None)[source]

Pretty-print the J table to stdout.

Return type:

None

Parameters:
mag4.cli.extract.main(argv=None)[source]
Return type:

int

CLI: multi-sublattice LSWT magnon dispersion + Tyablikov RPA T_c.

mag4.cli.magnon.parse_j_values(s)[source]

Parse "J1:0.26,J2:0.26"{"J1": 0.26, "J2": 0.26}.

Return type:

Dict[str, float]

Parameters:

s (str)

class mag4.cli.magnon.ExtractedJs(j_values, spin, dists, skipped, ring, sigmas, rms_meV)[source]

Bases: NamedTuple

Contents of a j_values.dat file, parsed for mag4-magnon.

Parameters:
j_values: Dict[str, float]

Alias for field number 0

spin: float | None

Alias for field number 1

dists: Dict[str, float]

Alias for field number 2

skipped: list

Alias for field number 3

ring: dict | None

Alias for field number 4

sigmas: Dict[str, float]

Alias for field number 5

rms_meV: float | None

Alias for field number 6

mag4.cli.magnon.parse_j_values_file(path)[source]

Read a j_values.dat written by mag4-extract.

Handles both the four-state format (name d JS2(eV) JS2(meV) J(meV) J(K) status) and the energy-mapping format (name d JS2(meV) [sig(meV)] ). j_values are the JS² values in meV (mag4-magnon’s native input); a Jring row’s JS2 column holds the fitted Jring·S⁴ and is returned separately in ring. The per-coupling standard errors (sig(meV) column, newer files) and the fit RMS from the header feed the statistical-significance test used by the dimensionality guard.

Return type:

ExtractedJs

Parameters:

path (str)

mag4.cli.magnon.apply_ring_renormalisation(j_values, shells, edge_label, js4_meV, *, tol=0.02)[source]

Fold a four-spin ring coupling into effective bilinear JS² (LSWT).

Harmonic (LSWT) reduction of the cyclic four-spin operator about the Néel state (cf. Coldea et al., PRL 86, 5377 (2001)): each pair operator inside the quartic term is multiplied by the classical value of its partner bond, so the magnon Hamiltonian is EXACTLY that of a bilinear model with, per bond and in native JS² units (Jring·S⁴ enters directly — no explicit S factors),

edge shell (2 plaquettes/bond): JS² → JS² − 2·JringS⁴ diagonal shell (1 plaquette/bond): JS² → JS² − 1·JringS⁴

edge_label names the shell the square plaquette is built on; the diagonal shell is located geometrically (distance = √2 × edge, within tol relative). If the diagonal shell carries no fitted J it is induced with JS² = −JringS⁴. Returns (new_j_values, lines) where lines document the transformation for the report.

Parameters:
mag4.cli.magnon.build_parser()[source]
Return type:

ArgumentParser

mag4.cli.magnon.main(argv=None)[source]
Return type:

int

CLI: M – L – M bridging-angle analysis from a CIF.

mag4.cli.bond_angles.build_parser()[source]
Return type:

ArgumentParser

mag4.cli.bond_angles.main(argv=None)[source]
Return type:

int

CLI: extract converged magnetic moments from a tree of WIEN2k .scf files.

Layer: WIEN2k-specific.

Beyond tabulating the per-site moments, it checks that every configuration reached its intended magnetic state — a config whose moment collapsed to ~0 (non-magnetic solution) or drifted far from the generated |MAGMOM| = 2·S has an unreliable total energy and would poison a J / Jring extraction.

mag4.cli.extract_moments.main(argv=None)[source]
Return type:

int