GPAW专题

GPAW QNA空间变化泛函

用Cu₃Au演示元素参数、空间参数场和常数参数对照。

更新于 2026-11-09约 12 分钟阅读按任务与环境选择适用配置
阅读本篇目录

用Cu₃Au演示元素参数、空间参数场和常数参数对照。

1 下载、提交与运行#

下载完整操作包,保存为gpaw-qna.zip。在集群解压后,普通批作业可以执行sbatch job.slurm;下面的直接运行命令用于已经分配好的CPU资源内,不要在登录节点运行生产计算。

python3 -m zipfile -e gpaw-qna.zip .
cd gpaw-qna
bash setup.sh
bash run.sh
bash verify.sh
bash plot.sh

脚本按1个MPI进程、1个线程设置运行环境。先完成电子结构计算,再提取数据并画图;原始结果在results/,两张图片在plots/。verify.sh用于与附件的原样模型对照;改变体系后应自行检查科学收敛,不能继续沿用原模型数值作为目标。

2 QNA输入与结构#

QNA允许根据元素设置µ、β,并构造随位置变化的参数场。本例采用Cu₃Au的L1₂有序结构,晶格常数3.74 Å,每胞4个原子;PW200 eV、2×2×2 k点、30条带、0.1 eV费米展宽,44个价电子。

原生QNA参数为alpha=2.0、setup_name='PBE'、type='qna-gga'、orbital_dependent=False。元素参数采用官方示例:Au=(0.125,0.1),Cu=(0.0795,0.005)。µ与β都无量纲,参数顺序不能交换,也不能随意把其他材料的经验值当作通用推荐。

GPAW QNA空间变化泛函:实际结构与计算数据1

3 按顺序做四个对照#

附件计算编译后端PBE、常数参数QNA、元素参数QNA,以及与QNA核实现相配的pyPBE。常数参数为µ=0.2195149727645171、β=0.06672455060314922。

计算 总能量/eV
编译后端PBE −2.3675616510
常数参数QNA −2.3648059415
元素参数QNA 278.3656834683
pyPBE −2.3648059415

常数QNA与匹配的Python PBE核在本例能量一致;编译PBE与该Python核存在约2.756 meV的实现差异,所以验证常数极限要选择一致的核,不能把它误判成k点或截断能造成的差异。不同泛函的原子参考和芯区贡献可能不同,不能把表中的绝对能量差直接当成合金形成能或稳定性结论。

4 查看实际空间参数#

calculate_spatial_parameters(atoms)导出µ(r)、β(r)。常数参数对照得到常数场,元素参数计算得到位于输入参数范围内的真实空间分布。图中粗网格斑块来自这个教学模型的实际网格,不代表已经做了参数场的精细网格收敛。

GPAW QNA空间变化泛函:实际结构与计算数据2

results/Cu3Au-QNA-elements/fields.npz还包含密度、本征值、加权占据、力、结构和两种参数场。换体系后先检查元素键名、PAW设置和参数来源,再检查收敛;QNA不负责自动为新元素拟合最优参数。

参考:GPAW QNA输入与说明。

完整计算输入#

以下是附件实际执行的run.py,含结构、计算器、提取流程与输出定义。先原样运行,再按上文说明更改模型;inputs.json同时记录示例条件与旧曲线/目标轨道信息。

展开完整Python输入
from pathlib import Path
import json, os, hashlib, numpy as np
from ase import Atoms
from ase.lattice.compounds import L1_2
from gpaw import GPAW, PW, Mixer, Davidson, FermiDirac
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
from types import SimpleNamespace
from ase import Atoms
from gpaw.xc.kernel import XCKernel
from gpaw.xc.gga import PurePythonGGAKernel
from gpaw.xc.qna import QNAKernel
mu = 0.2195149727645171
beta = 0.06672455060314922
n = np.logspace(-4, 2, 30)[None, :]
sigma = 0.05 * n ** (8 / 3)
kernels = {'compiledPBE': XCKernel('PBE'), 'pyPBE': PurePythonGGAKernel('pyPBE'), 'constantQNA': QNAKernel(SimpleNamespace(override_atoms=None, atoms=Atoms('H'), current_atom=0, parameters={'H': (mu, beta)}))}
probe = dict(density_Bohr_minus3=n, gradient_sigma_Bohr_minus8=sigma)
for name, kernel in kernels.items():
    e = np.zeros(n.shape[1])
    vn = np.zeros_like(n)
    vs = np.zeros_like(sigma)
    kernel.calculate(e, n.copy(), vn, sigma.copy(), vs)
    probe[name + '_energy_density_Ha_Bohr_minus3'] = e
    probe[name + '_dn'] = vn
    probe[name + '_dsigma'] = vs
for suffix in ['energy_density_Ha_Bohr_minus3', 'dn', 'dsigma']:
    ck('matched_python_kernel:' + suffix, abs(probe['pyPBE_' + suffix] - probe['constantQNA_' + suffix]).max() < 1e-12, value=float(abs(probe['pyPBE_' + suffix] - probe['constantQNA_' + suffix]).max()))
np.savez_compressed(R / 'kernel-probe.npz', **probe)
mu = 0.2195149727645171
beta = 0.06672455060314922
for name, parameters in [('Cu3Au-PBE', None), ('Cu3Au-QNA-PBE', {'Au': (mu, beta), 'Cu': (mu, beta)}), ('Cu3Au-QNA-elements', {'Au': (0.125, 0.1), 'Cu': (0.0795, 0.005)}), ('Cu3Au-pyPBE', None)]:
    D = R / name
    D.mkdir()
    a = L1_2(['Au', 'Cu'], latticeconstant=3.74)
    xc = ('pyPBE' if name == 'Cu3Au-pyPBE' else 'PBE') if parameters is None else dict(alpha=2.0, name='QNA', orbital_dependent=False, parameters=parameters, setup_name='PBE', type='qna-gga')
    c = GPAW(legacy_gpaw=True, mode=PW(200), xc=xc, nbands=30, kpts=(2, 2, 2), spinpol=False, occupations=FermiDirac(0.1), mixer=Mixer(0.15, 5, 50), 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())
    free = float(a.get_potential_energy(force_consistent=True))
    force = a.get_forces()
    rho = c.get_all_electron_density(gridrefinement=1)
    ne = float(rho.sum() * a.get_volume() / rho.size)
    occs = np.array([c.get_occupation_numbers(kpt=k) for k in range(c.wfs.kd.nibzkpts)])
    val = float(occs.sum())
    ck(name + ':AE166', abs(ne - 166) < 0.05, value=ne)
    ck(name + ':valence44', abs(val - 44) < 1e-07, value=val)
    ck(name + ':finite', np.isfinite([e, free, *force.ravel()]).all() and np.isfinite(rho).all())
    fields = dict(AE_total_density_e_A3=rho, weighted_occupations=occs, IBZ_weights=c.get_k_point_weights(), eigenvalues_eV=np.array([c.get_eigenvalues(kpt=k) for k in range(c.wfs.kd.nibzkpts)]), cell_A=a.cell.array, positions_A=a.positions, atomic_numbers=a.numbers, forces_eV_A=force)
    if parameters is not None:
        mg, bg = c.hamiltonian.xc.calculate_spatial_parameters(a)
        ck(name + ':mu_convex_bounds', mg.min() >= min((v[0] for v in parameters.values())) - 1e-12 and mg.max() <= max((v[0] for v in parameters.values())) + 1e-12)
        ck(name + ':beta_convex_bounds', bg.min() >= min((v[1] for v in parameters.values())) - 1e-12 and bg.max() <= max((v[1] for v in parameters.values())) + 1e-12)
        if name.endswith('PBE'):
            ck(name + ':constant_parameters_preserved', abs(mg - mu).max() < 1e-12 and abs(bg - beta).max() < 1e-12)
        else:
            ck(name + ':actual_spatial_parameter_variation', np.std(mg) > 0.0001 and np.std(bg) > 0.0001)
        fields['spatial_mu'] = mg
        fields['spatial_beta'] = bg
    np.savez_compressed(D / 'fields.npz', **fields)
    c.write(str(D / 'state.gpw'), mode='all')
    rows.append(dict(name=name, energy_eV=e, force_consistent_free_energy_eV=free, AE_electrons=ne, valence=val, parameters=parameters, forces_eV_A=force.tolist(), cell_A=a.cell.array.tolist(), positions_A=a.positions.tolist()))
    (R / 'PARTIAL-RESULTS.json').write_text(json.dumps(rows, indent=2) + '\n')
    print(name, e, 'valence', val, flush=True)
by = {r['name']: r for r in rows}
ck('constant_QNA_recovers_matched_python_PBE_energy', abs(by['Cu3Au-QNA-PBE']['energy_eV'] - by['Cu3Au-pyPBE']['energy_eV']) < 0.002, value=by['Cu3Au-QNA-PBE']['energy_eV'] - by['Cu3Au-pyPBE']['energy_eV'])
ck('constant_QNA_recovers_matched_python_PBE_forces', abs(np.array(by['Cu3Au-QNA-PBE']['forces_eV_A']) - by['Cu3Au-pyPBE']['forces_eV_A']).max() < 0.02)
for f, h in p['source_SHA'].items():
    ck('source_immutable:' + f, hashlib.sha256(Path(f).read_bytes()).hexdigest() == h)
compiled_difference = by['Cu3Au-QNA-PBE']['energy_eV'] - by['Cu3Au-PBE']['energy_eV']
v = dict(compiled_PBE_vs_constant_QNA_energy_difference_eV=compiled_difference, passed=all((x['passed'] for x in checks)), checks=len(checks), items=checks, results=rows, whole_group_complete=False, no_converged_lattice_binding_or_functional_accuracy_claim=True)
(R / 'VALIDATION.json').write_text(json.dumps(v, indent=2) + '\n')
print('408', 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/Cu3Au-PBE/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数据集或二进制检查点,运行时使用集群已安装数据集。

需要根据你的体系选择资源?联系讨论计算需求。实际可用软件与权限,以当前账户和运行环境为准。

查看配图

放大后可滚动查看细节,按 Esc 关闭。

100%查看原图 ↗