GPAW非共线磁矩与方向约束
用氢原子的四次自洽计算认识三分量磁矩、方向约束和自旋密度。
阅读本篇目录
用氢原子的四次自洽计算认识三分量磁矩、方向约束和自旋密度。
1 下载、提交与运行#
下载完整操作包,保存为gpaw-noncollinear.zip。在集群解压后,普通批作业可以执行sbatch job.slurm;下面的直接运行命令用于已经分配好的CPU资源内,不要在登录节点运行生产计算。
python3 -m zipfile -e gpaw-noncollinear.zip .
cd gpaw-noncollinear
bash setup.sh
bash run.sh
bash verify.sh
bash plot.sh
脚本按1个MPI进程、1个线程设置运行环境。先完成电子结构计算,再提取数据并画图;原始结果在results/,两张图片在plots/。verify.sh用于与附件的原样模型对照;改变体系后应自行检查科学收敛,不能继续沿用原模型数值作为目标。
2 先确定磁矩方向,再计算#
体系是4 Å立方盒中的一个H原子,位置为(2,2,2) Å;300 eV平面波、LDA、2条带、关闭对称性。使用新后端,打开SOC对应的实际输入键soc=True,初始磁矩为三分量向量。
操作包依次计算自由x、自由z、约束x、约束z四种情况。约束计算从倾斜方向开始,调用原生SpinDirectionConstraint,惩罚参数2 eV。先在完整脚本里查看磁矩、目标方向与约束对象的构造,再修改自己的模型;不要只改图例。
能量收敛1E−6、密度1E−5、本征态1E−7。单原子H的s态没有这里要比较的磁各向异性能,因此自由x与自由z的能量一致,是自旋旋转对照,不是材料磁各向异性能示例。
3 实际结果怎样读#
| 计算 | 总能量/eV | 最终磁矩方向 |
|---|---|---|
| 自由x | −1.3317513282 | x,模长约1 μB |
| 自由z | −1.3317513282 | z,模长约1 μB |
| 约束x | −1.3317513319 | x,横向分量约10⁻⁷ μB |
| 约束z | −1.3317513314 | z,横向分量约10⁻⁷ μB |

方向约束作用于PAW原子球磁矩的轴向,不固定磁矩大小,也不自动区分平行与反平行;本例从与目标点积为正的初态得到正向结果。这里约束项使用return_energy=False,不要把约束计算的报告能量解释为惩罚能或直接拿去求真实磁各向异性能。
4 读取自旋密度#
fields.npz保存真实全电子四分量场:电荷密度n与磁化密度mx、my、mz。电荷密度单位e/ų,磁化密度单位μB/ų。下面的二维切片来自实际网格,不是示意磁场。

读取占据时脚本用get_occupation_numbers(raw=True).copy(),在副本上乘简并和k点权重,避免该版本新后端默认接口的原地加权副作用。换成多原子磁性体系还需检查初始磁序、SOC、k网格、截断能和亚稳解。
参考:GPAW非共线与SOC说明。
完整计算输入#
以下是附件实际执行的run.py,含结构、计算器、提取流程与输出定义。先原样运行,再按上文说明更改模型;inputs.json同时记录示例条件与旧曲线/目标轨道信息。
展开完整Python输入
from pathlib import Path
import os, json, hashlib, numpy as np
from ase import Atoms
from gpaw.new.ase_interface import GPAW
from gpaw.new.constraints import SpinDirectionConstraint
from ase.units import Hartree
R = Path(__file__).resolve().parent / 'results'
R.mkdir()
p = json.loads((R.parent / 'inputs.json').read_text())
rows = []
checks = []
def ck(n, b, **kw):
checks.append(dict(name=n, passed=bool(b), **kw))
for f, h in p['source_SHA'].items():
assert hashlib.sha256(Path(f).read_bytes()).hexdigest() == h
cases = [('free-x', [1.0, 0, 0], None), ('free-z', [0, 0, 1.0], None), ('constraint-x', [1.0, 0.9, 0.9], [1.0, 0, 0]), ('constraint-z', [0.9, 0.9, 1.0], [0, 0, 1.0])]
for name, initial, target in cases:
D = R / name
D.mkdir()
a = Atoms('H', positions=[[2.0, 2.0, 2.0]], cell=[4.0] * 3, pbc=False)
m = np.array([initial])
m /= np.linalg.norm(m)
constraint = SpinDirectionConstraint({0: target}, penalty=2.0) if target is not None else None
c = GPAW(mode={'name': 'pw', 'ecut': 300}, xc='LDA', nbands=2, symmetry='off', eigensolver={'niter': 8}, mixer={'backend': 'msr1'}, soc=True, magmoms=m, parallel={'domain': 1, 'band': 1}, extensions=[] if constraint is None else [constraint], convergence=dict(energy=1e-06, density=1e-05, eigenstates=1e-07), maxiter=200, txt=str(D / 'native.txt'))
a.calc = c
e = float(a.get_potential_energy())
c.write(str(D / 'state.gpw'), mode='all')
mv, ma = c.dft.density.calculate_magnetic_moments()
rho = c.dft.densities().all_electron_densities(grid_spacing=0.2)
data = np.array(rho.data)
integral = data.reshape((4, -1)).sum(1) * a.get_volume() / np.prod(data.shape[1:])
raw = c.get_occupation_numbers(raw=True).copy()
ne = float(raw.sum() * c.dft.ibzwfs.spin_degeneracy)
D4 = np.array(c.dft.density.D_asii[0])
N = np.array(c.dft.density.N0_aii[0])
local = np.einsum('vij,ij->v', D4[1:4].real, N)
ck(name + ':noncollinear_four_components', data.shape[0] == 4 and c.dft.density.ncomponents == 4)
ck(name + ':finite', np.isfinite([e, ne, *mv, *ma.ravel(), *integral]).all() and np.isfinite(data).all())
ck(name + ':one_valence_electron', abs(ne - 1) < 1e-08, value=ne)
ck(name + ':AE_grid_one_electron', abs(integral[0] - 1) < 0.02, value=float(integral[0]))
ck(name + ':magnetic_norm1', abs(np.linalg.norm(mv) - 1) < 0.003, value=float(np.linalg.norm(mv)))
ck(name + ':AE_magnetization_matches_native', abs(integral[1:] - mv).max() < 0.02)
ck(name + ':PAW_local_moment_reconstruction', abs(local - ma[0]).max() < 1e-10)
direction = np.array(initial if target is None else target, dtype=float)
direction /= np.linalg.norm(direction)
perp = mv - direction * (mv @ direction)
ck(name + ':spin_axis_target', np.linalg.norm(perp) < 0.01, value=float(np.linalg.norm(perp)))
fields = dict(AE_density_and_magnetization=data, cell_A=a.cell.array, positions_A=a.positions, native_total_magnetic_moment=mv, native_atomic_magnetic_moments=ma, atomic_spin_density_matrices=D4, PAW_inner_product=N, raw_occupations=raw, eigenvalues_eV=c.get_eigenvalues(), initial_magnetization=m, target_axis=direction)
if constraint is not None:
setup = c.dft.setups[0]
M = D4[1:4].real
penalty, V = constraint.calculate(M, 0, setup.l_j, setup.N0_q, return_energy=True)
expected = 2 / Hartree * (local @ local - (direction @ local) ** 2)
ck(name + ':penalty_from_actual_PAW_moment', abs(penalty - expected) < 1e-12)
dh = 1e-05
plus = M.copy()
minus = M.copy()
plus[0, 0, 0] += dh / 2
minus[0, 0, 0] -= dh / 2
ep = constraint.calculate(plus, 0, setup.l_j, setup.N0_q, return_energy=True)[0]
em = constraint.calculate(minus, 0, setup.l_j, setup.N0_q, return_energy=True)[0]
ck(name + ':actual_density_penalty_derivative', abs((ep - em) / dh - V[0, 0, 0]) < 1e-08)
fields['constraint_Hamiltonian_Ha'] = V
fields['penalty_energy_Ha'] = np.array(penalty)
np.savez_compressed(D / 'fields.npz', **fields)
rows.append(dict(name=name, energy_eV=e, magnetic_moment=mv.tolist(), atomic_magnetic_moments=ma.tolist(), AE_integrals=integral.tolist(), valence=ne, initial=m.tolist(), target=target))
(R / 'PARTIAL-RESULTS.json').write_text(json.dumps(rows, indent=2) + '\n')
print(name, e, mv, flush=True)
by = {x['name']: x for x in rows}
ck('H_x_z_spin_rotation_energy_invariance', abs(by['free-x']['energy_eV'] - by['free-z']['energy_eV']) < 2e-05, value=by['free-x']['energy_eV'] - by['free-z']['energy_eV'])
for f, h in p['source_SHA'].items():
ck('source_immutable:' + f, hashlib.sha256(Path(f).read_bytes()).hexdigest() == h)
v = dict(passed=all((c['passed'] for c in checks)), checks=len(checks), items=checks, results=rows, whole_group_complete=False, penalty_selects_axis_not_sign=True, no_converged_box_or_magnetic_anisotropy_claim=True)
(R / 'VALIDATION.json').write_text(json.dumps(v, indent=2) + '\n')
print('404', v['passed'], v['checks'], flush=True)
assert v['passed']
终端查看结果#
计算后先查看逐项结果,再打开图。下面只读取输出,不会启动新的自洽:
python3 -c "import json; v=json.load(open('results/VALIDATION.json')); print(v.get('results')); print(v.get('summary', {}))"
source env.sh
python3 -c "import numpy as np; d=np.load('results/constraint-x/fields.npz'); print([(k, d[k].shape) for k in d.files])"
ls -lh plots/*.png
环境:GPAW26.7.0、GCC13.3、OpenMPI5.0.10rc2、MKL2023.2、Libxc7.0、Python3.11.16。模块为gpaw/26.7.0-8173m;实际输入由Python/ASE组织,使用附件的env.sh加载一致环境。附件不打包PAW数据集或二进制检查点,运行时使用集群已安装数据集。