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
CrystalDatadirectly from a CIF file (no POSCAR needed).- Parameters:
- Raises:
ValueError – If
elementis not present in the structure.- Return type:
- mag4.read_poscar(poscar, species_index)[source]
Parse a VASP POSCAR / CONTCAR file.
- Parameters:
- Return type:
- mag4.write_poscar(crystal, name)[source]
Write a single POSCAR for the supercell geometry. Returns the file path.
- Return type:
- Parameters:
crystal (CrystalData)
name (str)
- 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.
- 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}.
- 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}, …].
- 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:
- Returns:
Lattice matrix with the three lattice vectors as columns.
- Return type:
ndarray, shape (3, 3)
- mag4.get_cartesian_coords(atoms, lat_mat)[source]
Cartesian coordinates of a list of atoms in the legacy
[label, x, y, z, …]format.
- mag4.expand_supercell(crystal, na, nb, nc)[source]
Expand a
CrystalDataby(na × nb × nc).Returns the original instance unchanged when na = nb = nc = 1.
- Return type:
- Parameters:
crystal (CrystalData)
na (int)
nb (int)
nc (int)
- mag4.check_coupling_spacing(couplings, user_tol)[source]
Inspect spacing between consecutive J distances and return a safe dimer-search tolerance.
Warns when:
--tolis 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/J1bproduced 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 thesafe_tolcomputation so the dimer search can still use a non-zero distance tolerance. The angle filter infind_dimers()keeps the sub-shells apart.
- 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=Falseto silence info logs and the missing-coupling warning block; useful when scanning many trial supercells.- Return type:
- 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:
Build a supercell large enough that every coupling distance d_Jn appears as a genuine intra-cell pair (direct Cartesian distance).
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
--supercellflag to try.Angle-aware mode
When
structure(apymatgen.core.Structureof the same supercell, with the magnetic atoms placed at indices0..N_mag-1— seemag4.geometry.crystal_to_pymatgen()) andligand_elementsare supplied, eachcouplingsentry 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 siteiis 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.CrystalDataof 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 withmag4.geometry.filter_symops_by_fingerprint()if ligand environments matter).
- Return type:
- mag4.build_reference_bath(n_atoms, user_ref=None)[source]
Build the reference magnetic bath.
Default behaviour is fully ferromagnetic (all +1). Pass
user_refas a list of ±1 of lengthn_atomsto override.- Raises:
ValueError – If the length is wrong or values other than ±1 are present.
- Return type:
- Parameters:
- mag4.generate_four_states(ref_bath, dimer, coupling_name)[source]
Generate the four spin configurations
uu, dd, ud, dufor 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:
- 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_opsplus, whentime_reversalis 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=Falserestricts to the unitary subgroup — used by--check-degeneracyto 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:
- 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).couplingsis a list of items that can be fed straight intomag4.dimers.find_dimers():Without
ligand_elements:[[label, distance], ...](legacy form, distance is the only discriminator).With
ligand_elementsset:[[label, distance, fingerprint], ...]wherefingerprintis anmag4.geometry.AngleFingerprintcarrying the M–L–M bridging geometry — used byfind_dimersas a secondary discriminator so that pairs at the same distance but different bridging angles end up in different J shells (J1a,J1b, …).
- mag4.magmom_to_udn(values)[source]
Translate a flat list of MAGMOM values to the
u/d/nletters.- 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:
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 matchesmag4.vasp.build_magmom_lines().- Return type:
- Parameters:
unique_groups (List[List[FourStateConfig]])
crystal (CrystalData)
spin_magnitude (float)
- mag4.find_instgen()[source]
Locate the WIEN2k
instgenscript.Lookup order:
$WIEN2k_INSTGENenvironment variable, if set.instgenon$PATH.instgen_lapwon$PATH(the canonical WIEN2k name).$WIENROOT/instgen_lapw.~/bin/instgen.
Returns the absolute path or
Noneif not found.
- 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.instfile inconfigN/.The output file is named after the directory (
config1.instinsideconfig1/, etc.) so that WIEN2k’s standardcase == basename(pwd)convention works directly on the user’s HPC machine.- Parameters:
unique_groups (
List[List[FourStateConfig]]) – Same conventions asmag4.vasp.build_magmom_lines().crystal (
CrystalData) – Same conventions asmag4.vasp.build_magmom_lines().spin_magnitude (
float) – Same conventions asmag4.vasp.build_magmom_lines().parent_dir (str) – Compound (or run) directory hosting the
configN/subdirectories.instgen_path (str, optional) – Path to the
instgenscript. Auto-detected viafind_instgen()if omitted.
- Returns:
Paths of the written
configN.instfiles, in config order.- Return type:
- mag4.write_case_struct_files(crystal, n_configs, parent_dir, *, rmt=2.0, npt=781, r0=None)[source]
Write
configN.structinto everyconfigN/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.
- 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.structfrom aCrystalData.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 inconfigN.structmatches the order assumed byconfigN.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 bymag4.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 whenNone.
- Return type:
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)forname, NPT, R0, RMT, Zrespectively. 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:
objectOutcome of parsing one DFT output file.
- Parameters:
- energy_eV
Last total energy reported in the file, expressed in eV.
Nonewhen 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:
- mag4.extract_vasp_energy(outcar_path)[source]
Parse a VASP
OUTCARand return its converged total energy.- Convergence rule (simple, code-agnostic):
convergedisTruewhen all of the following hold:the file exists and is readable,
at least one
free energy TOTENline was parsed,no
WARNING: ... NELM = Nline was emitted,the SCF actually converged — either VASP’s electronic marker
aborting loop because EDIFF is reachedor the ionicreached required accuracyappears — 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
diagnosticsfor the raw flags so the caller can decide what to do.
- Returns:
Always non-
None; check.energy_eVand.convergedto interpret the outcome.- Return type:
- 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:
- Parameters:
scf_path (str)
- Convergence rule (simple):
convergedisTruewhen all of the following hold:the file exists and is readable,
at least one
:ENEline 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 inEnergyResult.last_disand the verbatim:DISline inEnergyResult.last_dis_lineso 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:vasp→OUTCARwien2k→ SCF file (seescf_basename).
scf_basename (str, optional) – WIEN2k only. Basename (with or without the
.scfsuffix) of the SCF file to parse insideconfig_dir. Useful when several SCF runs are stored side by side under names likepbe.scf,r2scan.scf. When omitted (default) the standard WIEN2k convention<basename(config_dir)>.scfis used — i.e.config5/config5.scfforconfig5/.
- Return type:
- 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:
objectOne row of the EXTRACTION FORMULAS section.
- Parameters:
- 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:
- 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:
- 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:
objectOne row of the final J table emitted by
compute_j_values().- Parameters:
- energies_eV
Per-state total energies in eV. Keys:
"uu","dd","ud","du".Nonewhen the corresponding config did not converge (or didn’t exist).
- JS2_eV, JS2_meV
Computed
JS² = (E_uu + E_dd − E_ud − E_du) / 4.Noneif the formula could not be evaluated (any energy missing).- Type:
float or None
- J_meV, J_K
Computed
J = JS² / S²in meV and Kelvin (J / k_B). Only populated when a positive spinSwas passed.- Type:
float or None
- mag4.parse_report_formulas(report_path)[source]
Extract every
J … = ( E[#…] + …) / 4line from a report file.- Parameters:
report_path (str) – Path to a
four_state_report.datproduced bymag4-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_pathdoesn’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 ofparse_report_formulas().energies (dict[int, EnergyResult]) – Map
config_index → EnergyResultfor every config the formulas reference. Indices come from theconfigN/dir name, 1-based. Configs that didn’t converge MUST still be present withEnergyResult.converged = Falseso this function can attribute the failure correctly.spin (float, optional) – Magnetic spin quantum number
S. When given (>0), eachJS²is divided byS²to also reportJin meV and in Kelvin.config_n_atoms (dict[int, int], optional) – Map
config_index → n_atomsfromparse_report_config_cells(). When supplied, each contributing config’s total energy is rescaled byN_LCM / N_Xbefore applying the four-state formula — necessary whenmag4-magneticwrote 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
config1…configNare read. Useful to short-circuit on very large compound dirs.scf_basename (str | None)
- Returns:
Map
config_index → EnergyResult. Empty if noconfigN/directory was found.- Return type:
- 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
Modules
Geometric / post-DFT analyses (bridging-angle scans, WIEN2k moment extraction). |
|
Command-line entry points exposed via |
|
Physical constants and shared data structures. |
|
Dimer search for the four-state method (direct intra-cell Cartesian distances). |
|
DFT total-energy parsers (VASP |
|
Energy-mapping (linear least-squares) configuration generator. |
|
J-value extraction from a completed four-state DFT campaign. |
|
Linear least-squares (energy-mapping) estimator for Heisenberg couplings. |
|
Geometric fingerprints for J-shell discrimination via M–X–M bond angles. |
|
CIF reading: |
|
VASP POSCAR I/O — read into |
|
Lattice geometry helpers (matrix construction, fractional ↔ Cartesian, PBC distances). |
|
Multi-sublattice linear spin-wave theory (LSWT) and Tyablikov RPA. |
|
High-level CIF analysis pipeline (loads structure, prints distance tables, returns crystal + couplings). |
|
Pretty-printers for distance tables, J-shell summaries and four-state reports. |
|
16-state method for the four-spin ring (cyclic) exchange |
|
Generate, group and merge the four spin configurations of the four-state method. |
|
Build (na × nb × nc) supercells from a |
|
Site-permutation symmetry operations on the magnetic sublattice. |
|
VASP input generation: per-config POSCAR / INCAR / KPOINTS for the four-state pipeline. |
|
WIEN2k input generation: |