"""High-symmetry k-path generation (auto via pymatgen, or user-supplied label string)."""
from __future__ import annotations
import logging
import warnings
from typing import Dict, List, Tuple
import numpy as np
log = logging.getLogger(__name__)
def _clean_label(s: str) -> str:
"""Normalise a high-symmetry point label to unicode Γ."""
for v in ("\\Gamma", "GAMMA", "Gamma", "GM"):
s = s.replace(v, "Γ")
if s == "G":
s = "Γ"
return s
def _build_custom_kpath_arrays(custom_str: str,
hs_frac: Dict,
rec_matrix: np.ndarray,
n_points: int):
"""Build kpath arrays from a user-supplied label string.
Labels are space-separated HS point names. ``|`` marks a discontinuity.
Example: ``"Γ M K Γ | A L H"``.
"""
hs_norm = {_clean_label(k): np.array(v, dtype=float)
for k, v in hs_frac.items()}
def lookup(raw_label: str) -> np.ndarray:
clean = _clean_label(raw_label)
if clean in hs_norm:
return hs_norm[clean].copy()
for k, v in hs_norm.items():
if k.upper() == clean.upper():
return v.copy()
raise ValueError(
f"Label '{raw_label}' not found.\n"
f" Available: {sorted(hs_norm.keys())}\n"
f" Tip: use 'G' or 'Gamma' for Γ.")
tokens = custom_str.split()
segments: List[List[str]] = []
current: List[str] = []
for tok in tokens:
if tok == "|":
if current:
segments.append(current)
current = []
else:
current.append(_clean_label(tok))
if current:
segments.append(current)
if not segments:
raise ValueError("--kpath string is empty.")
all_frac: List[np.ndarray] = []
tick_raw: List[Tuple[int, str]] = []
break_indices: set = set()
for s_idx, seg in enumerate(segments):
if len(seg) < 1:
continue
seg_start = len(all_frac)
all_frac.append(lookup(seg[0]))
tick_raw.append((seg_start, seg[0]))
for i in range(1, len(seg)):
f_prev = lookup(seg[i - 1])
f_curr = lookup(seg[i])
for k in range(1, n_points + 1):
t = k / n_points
all_frac.append(f_prev + t * (f_curr - f_prev))
tick_raw.append((len(all_frac) - 1, seg[i]))
if s_idx > 0:
break_indices.add(seg_start - 1)
break_indices.add(seg_start)
kpoints_frac = np.array(all_frac, dtype=float)
kpoints_cart = kpoints_frac @ rec_matrix
seg_dists = np.linalg.norm(np.diff(kpoints_cart, axis=0), axis=1)
distances = np.concatenate([[0.0], np.cumsum(seg_dists)])
tick_positions: List[float] = []
tick_labels: List[str] = []
for idx, lbl in tick_raw:
d = float(distances[idx])
if tick_positions and abs(d - tick_positions[-1]) < 1e-8:
prev = tick_labels[-1]
if lbl != prev and lbl not in prev.split("|"):
tick_labels[-1] = prev + "|" + lbl
else:
tick_positions.append(d)
tick_labels.append(lbl)
return (kpoints_frac, kpoints_cart, distances,
break_indices, tick_positions, tick_labels)
[docs]
def get_kpath(structure, n_points: int = 100,
custom_labels: str = None) -> Dict:
"""Generate a high-symmetry k-path using pymatgen :class:`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
-------
dict
Keys: ``kpoints_cart, kpoints_frac, distances, tick_positions,
tick_labels, break_indices, prim_rec_matrix, hs_frac``.
"""
from pymatgen.symmetry.bandstructure import HighSymmKpath
sg_num = structure.get_space_group_info()[1]
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore",
message=".*does not match the expected standard primitive.*")
kpath_obj = HighSymmKpath(structure)
rec = structure.lattice.reciprocal_lattice
frac_list_full, labels_list_full = kpath_obj.get_kpoints(
line_density=max(n_points, 10), coords_are_cartesian=False)
kf_full = np.array(frac_list_full, dtype=float)
hs_frac: Dict[str, np.ndarray] = {}
for i, lbl in enumerate(labels_list_full):
if lbl == "":
continue
clean = _clean_label(lbl)
key = clean.split("|")[0]
if key not in hs_frac:
hs_frac[key] = kf_full[i]
if custom_labels is not None:
log.info("Using custom k-path: %s", custom_labels)
(kpoints_frac, kpoints_cart, distances,
break_indices, tick_positions, tick_labels) = \
_build_custom_kpath_arrays(
custom_labels, hs_frac, rec.matrix, n_points)
source = "custom"
else:
frac_list, labels_list = kpath_obj.get_kpoints(
line_density=n_points, coords_are_cartesian=False)
kpoints_frac = np.array(frac_list, dtype=float)
kpoints_cart = kpoints_frac @ rec.matrix
seg_dists = np.linalg.norm(np.diff(kpoints_cart, axis=0), axis=1)
distances = np.concatenate([[0.0], np.cumsum(seg_dists)])
labeled_indices = [i for i, lb in enumerate(labels_list) if lb != ""]
break_indices = set()
for k in range(1, len(labeled_indices)):
ia = labeled_indices[k - 1]
ib = labeled_indices[k]
if ib == ia + 1:
d_step = float(np.linalg.norm(
kpoints_cart[ib] - kpoints_cart[ia]))
if d_step > 1e-6:
break_indices.add(ia)
break_indices.add(ib)
tick_positions, tick_labels_raw = [], []
for i, lbl in enumerate(labels_list):
if lbl == "":
continue
if tick_positions and abs(distances[i] - tick_positions[-1]) < 1e-8:
prev = tick_labels_raw[-1]
if lbl != prev and lbl not in prev.split("|"):
tick_labels_raw[-1] = prev + "|" + lbl
else:
tick_positions.append(float(distances[i]))
tick_labels_raw.append(lbl)
tick_labels = [_clean_label(lb) for lb in tick_labels_raw]
source = "auto"
if sg_num <= 2: latt_type = "triclinic"
elif sg_num <= 15: latt_type = "monoclinic"
elif sg_num <= 74: latt_type = "orthorhombic"
elif sg_num <= 142: latt_type = "tetragonal"
elif sg_num <= 167: latt_type = "trigonal"
elif sg_num <= 194: latt_type = "hexagonal"
else: latt_type = "cubic"
log.info("k-path (%s) | %s | %d pts | %d ticks",
source, latt_type, len(kpoints_frac), len(tick_labels))
log.info(" Path: %s", " -- ".join(tick_labels))
log.info(" Breaks: %d", len(break_indices) // 2)
log.info(" Available HS points:")
for lbl, frac in sorted(hs_frac.items()):
log.info(" %-10s frac=(%+.6f, %+.6f, %+.6f) "
"cart=(%+.4f, %+.4f, %+.4f) A-1",
lbl, frac[0], frac[1], frac[2], *(frac @ rec.matrix))
return dict(
kpoints_cart = kpoints_cart,
kpoints_frac = kpoints_frac,
distances = distances,
tick_positions = tick_positions,
tick_labels = tick_labels,
break_indices = break_indices,
prim_rec_matrix = rec.matrix,
hs_frac = hs_frac,
)