Source code for mag4.magnon.ordering

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

from __future__ import annotations

import logging
from typing import Dict

import numpy as np

log = logging.getLogger(__name__)


[docs] def analyse_ordering(Jq, evals, q_frac, q_cart, structure) -> Dict: """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``. """ lambda_min_q = evals[:, -1] idx0 = int(np.argmin(lambda_min_q)) lam0 = float(lambda_min_q[idx0]) q0_c = q_cart[idx0] q0_f = q_frac[idx0] Jq_herm = 0.5 * (Jq[idx0] + Jq[idx0].T.conj()) _, ev_vecs = np.linalg.eigh(Jq_herm) eigvec = ev_vecs[:, 0].real def frac_to_ratio(x, max_denom=8): x = float(x) best, best_err = (0, 1), abs(x) for m in range(1, max_denom + 1): p = round(x * m) err = abs(x - p / m) if err < best_err: best_err = err best = (p, m) return best tol_q = 1.0 / (2 * max(len(lambda_min_q) ** (1/3), 10)) ratios = [frac_to_ratio(q) for q in q0_f] sc_dims = [r[1] for r in ratios] commensurate = all(abs(q0_f[i] - ratios[i][0]/ratios[i][1]) < tol_q for i in range(3)) q0_norm = float(np.linalg.norm(q0_c)) if q0_norm < 0.01: signs = np.sign(eigvec) if np.all(signs == signs[0]): character = "FM (Curie)" else: n_up = int(np.sum(signs > 0)) n_dn = int(np.sum(signs < 0)) character = f"Type-I AFM at q=0 [{n_up} up, {n_dn} down sublattice(s)]" elif commensurate: sc_str = "×".join(str(s) for s in sc_dims) character = ("Commensurate ordering q₀=(" + ", ".join(f"{p}/{m}" for p, m in ratios) + f") magnetic supercell: {sc_str}") else: character = ("Incommensurate spiral q₀=(" + ", ".join(f"{q:.4f}" for q in q0_f) + ") — LSWT requires spin-spiral treatment") return dict(idx0=idx0, q0_cart=q0_c, q0_frac=q0_f, lam0=lam0, eigvec=eigvec, ratios=ratios, character=character, sc_dims=sc_dims, commensurate=commensurate)
[docs] def get_spin_dirs_from_q0(bond_data: Dict, q0_frac: np.ndarray) -> np.ndarray: """Assign collinear spin directions in the primitive cell from the ordering wavevector. .. math:: 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. """ prim_fracs = bond_data["prim_fracs"] phases = 2.0 * np.pi * (prim_fracs @ q0_frac) spin_dirs = np.sign(np.cos(phases)) spin_dirs[spin_dirs == 0.0] = 1.0 log.info(" Spin directions from cos(2π q₀·r_α): %d up, %d down", int(np.sum(spin_dirs > 0)), int(np.sum(spin_dirs < 0))) return spin_dirs