atomscf.hf.exchange 源代码

r"""HF 交换算子模块

本模块实现 Hartree-Fock 非局域交换算子的径向中心场形式。

物理背景
========

在径向 HF 中心场近似下,交换算子对目标态 u_l(r) 的作用为:

.. math::

    K_\ell[u](r) = -\sum_{\ell'} \sum_{k} a_k(\ell,\ell')
    \sum_i n_i \cdot R^k_{i,u}(r) \cdot u_{\ell',i}(r)

其中:
- :math:`a_k(\ell,\ell')` 是角动量耦合因子
- :math:`R^k_{i,u}(r)` 是 Slater 径向积分
- :math:`n_i` 是占据态 i 的占据数
- 求和遍历所有占据态 i(轨道 :math:`\ell'`)

s 轨道特化
==========

对于 s 轨道(l=0),选择规则简化为:
- 仅允许 k=0(库仑项)
- a_0(0,0) = 1.0
- 交换仅与其他 s 轨道耦合

交换公式变为:

.. math::

    K_0[u](r) = -\sum_{i \in s} n_i \cdot R^0_{i,u}(r) \cdot u_i(r)

实现策略
========

本模块采用**算子-作用式**(operator-action formulation):

1. 交换算子返回闭包函数 `K[·]`,而非显式矩阵
2. 每次调用 `K[u]` 时实时计算 Slater 积分(避免 O(N²) 存储)
3. 占据态间的 R^k_{i,j} 可缓存(数量有限)
4. 目标态 u 的 R^k_{i,u} 每次重算(避免存储爆炸)

使用示例
========

.. code-block:: python

    from atomscf.hf import SlaterIntegralCache, exchange_operator_s
    from atomscf.grid import radial_grid_linear, trapezoid_weights

    # 准备网格
    r, _ = radial_grid_linear(n=1000, rmin=1e-6, rmax=50.0)
    w = trapezoid_weights(r)

    # 占据态(例:氢原子 1s)
    u_1s = np.sqrt(2) * np.exp(-r)
    u_occ = [u_1s]
    occ_nums = [1.0]

    # 创建交换算子
    cache = SlaterIntegralCache()
    K = exchange_operator_s(r, w, u_occ, occ_nums, cache=cache)

    # 应用到目标态
    Ku = K(u_1s)  # 返回 K[u_1s](r)

References
----------
.. [HFExchange] Cowan, R. D. (1981)
   "The Theory of Atomic Structure and Spectra"
   University of California Press, Chapter 7
.. [SlaterIntegrals] Slater, J. C. (1960)
   "Quantum Theory of Atomic Structure"
   McGraw-Hill, Volume 1
"""

from __future__ import annotations

from typing import Callable

import numpy as np

from .angular import get_coupling_factor
from .slater import SlaterIntegralCache, slater_integral_radial

__all__ = [
    "exchange_operator_s",
    "exchange_operator_general",
    "exchange_operator_general_spin",
]


[文档] def exchange_operator_s( r: np.ndarray, w: np.ndarray, u_occ: list[np.ndarray], occ_nums: list[float], cache: SlaterIntegralCache | None = None, ) -> Callable[[np.ndarray], np.ndarray]: r"""创建 s 轨道 (l=0) 交换算子。 返回闭包函数 K[u],对目标波函数 u 应用 HF 交换作用: .. math:: K[u](r) = -\sum_i n_i \cdot a_0(0,0) \cdot R^0_{i,u}(r) \cdot u_i(r) 由于 a_0(0,0) = 1.0,公式简化为: .. math:: K[u](r) = -\sum_i n_i \cdot R^0_{i,u}(r) \cdot u_i(r) Parameters ---------- r : np.ndarray 径向网格点(长度 n) w : np.ndarray 积分权重(长度 n) u_occ : list[np.ndarray] 占据态径向波函数列表(每个长度 n) occ_nums : list[float] 占据数列表(对应 u_occ) cache : SlaterIntegralCache | None, optional Slater 积分缓存(默认 None,内部创建新缓存) Returns ------- Callable[[np.ndarray], np.ndarray] 交换算子闭包 K[u],接受波函数 u(r) 返回 K[u](r) Notes ----- **性能优化**: - 占据态间 R^0_{i,j} 可预先缓存 - 目标态 u 的 R^0_{i,u} 实时计算(避免存储) **符号约定**: - 负号来自 HF 交换的物理定义 - 对闭壳层自旋求和已隐含在 n_i 中 Examples -------- 氢原子 1s 自交换:: >>> r, _ = radial_grid_linear(n=1000, rmin=1e-6, rmax=50.0) >>> w = trapezoid_weights(r) >>> u_1s = np.sqrt(2) * np.exp(-r) # 归一化径向波函数 >>> K = exchange_operator_s(r, w, [u_1s], [1.0]) >>> Ku = K(u_1s) >>> # Ku(r) ≈ -u_1s(r) / r 在远处 See Also -------- exchange_operator_general : 任意 l 的通用交换算子 slater_integral_k0 : k=0 Slater 积分特化版本 """ if cache is None: cache = SlaterIntegralCache() if len(u_occ) != len(occ_nums): raise ValueError( f"占据态数量 ({len(u_occ)}) 与占据数数量 ({len(occ_nums)}) 不匹配" ) # 预计算耦合因子(s-s 仅 k=0,且 a_0 = 1.0) a_0 = get_coupling_factor(0, 0, 0, use_cache=True) assert np.isclose(a_0, 1.0), "s-s 耦合因子应为 1.0" def apply_exchange(u_target: np.ndarray) -> np.ndarray: """应用交换算子到目标波函数。 Parameters ---------- u_target : np.ndarray 目标径向波函数(长度 n) Returns ------- np.ndarray K[u_target](r) """ if u_target.shape != r.shape: raise ValueError( f"目标波函数形状 {u_target.shape} 与网格 {r.shape} 不匹配" ) # 初始化交换势 Ku = np.zeros_like(r) # 遍历所有占据态 for i, (u_i, n_i) in enumerate(zip(u_occ, occ_nums)): # 计算 Slater 积分 R^0_{i,u}(r) # 注意:这里 i_index 用于缓存判断(实际上目标态不缓存) R0 = slater_integral_radial(r, w, u_i, u_target, k=0) # 累加交换贡献:-n_i * a_0 * R^0 * u_i # 由于 a_0 = 1.0,简化为:-n_i * R^0 * u_i Ku -= n_i * R0 * u_i return Ku return apply_exchange
[文档] def exchange_operator_general( r: np.ndarray, w: np.ndarray, l_target: int, u_occ_by_l: dict[int, list[np.ndarray]], occ_nums_by_l: dict[int, list[float]], cache: SlaterIntegralCache | None = None, ) -> Callable[[np.ndarray], np.ndarray]: r"""创建任意角动量 l 的通用交换算子。 返回闭包函数 K[u],对目标态 u_l(r) 应用完整 HF 交换: .. math:: K_\ell[u](r) = -\sum_{\ell'} \sum_{k} a_k(\ell,\ell') \sum_i n_i \cdot R^k_{i,u}(r) \cdot u_{\ell',i}(r) Parameters ---------- r : np.ndarray 径向网格点 w : np.ndarray 积分权重 l_target : int 目标态的角动量量子数 u_occ_by_l : dict[int, list[np.ndarray]] 按角动量分组的占据态波函数 格式:{l: [u_1, u_2, ...]} occ_nums_by_l : dict[int, list[float]] 按角动量分组的占据数 格式:{l: [n_1, n_2, ...]} cache : SlaterIntegralCache | None, optional Slater 积分缓存 Returns ------- Callable[[np.ndarray], np.ndarray] 交换算子闭包 K[u] Notes ----- **角动量耦合**: - 不同 l 通道通过允许的 k 值耦合 - 例:p-p 允许 k=[0,2],s-p 允许 k=[1] **性能**: - 复杂度 O(N_occ * n),n 为网格点数 - 缓存占据态间积分减少重复计算 Examples -------- 碳原子 HF(1s² 2s² 2p²):: >>> u_occ_by_l = { ... 0: [u_1s, u_2s], # s 轨道 ... 1: [u_2p], # p 轨道 ... } >>> occ_nums_by_l = { ... 0: [2.0, 2.0], # 1s² 2s² ... 1: [2.0], # 2p²(球对称平均) ... } >>> K_p = exchange_operator_general(r, w, l_target=1, ... u_occ_by_l, occ_nums_by_l) >>> Ku_2p = K_p(u_2p) See Also -------- exchange_operator_s : s 轨道特化版本 allowed_k_values : k 值选择规则 """ if cache is None: cache = SlaterIntegralCache() # 验证输入一致性 if set(u_occ_by_l.keys()) != set(occ_nums_by_l.keys()): raise ValueError("u_occ_by_l 和 occ_nums_by_l 的 l 键不一致") for l_val in u_occ_by_l: if len(u_occ_by_l[l_val]) != len(occ_nums_by_l[l_val]): raise ValueError(f"l={l_val} 的波函数数量与占据数数量不匹配") def apply_exchange(u_target: np.ndarray) -> np.ndarray: """应用通用交换算子。""" if u_target.shape != r.shape: raise ValueError(f"目标波函数形状 {u_target.shape} 与网格 {r.shape} 不匹配") Ku = np.zeros_like(r) # 遍历所有占据态(按 l 分组) for l_occ, u_list in u_occ_by_l.items(): n_list = occ_nums_by_l[l_occ] # 导入允许的 k 值 from .angular import allowed_k_values # 获取允许的 k 值 k_values = allowed_k_values(l_target, l_occ) # 遍历允许的多极指标 k for k in k_values: # 获取耦合因子 a_k = get_coupling_factor(l_target, k, l_occ, use_cache=True) if np.abs(a_k) < 1e-15: continue # 跳过零耦合 # 遍历该 l 通道的所有占据态 for i, (u_i, n_i) in enumerate(zip(u_list, n_list)): # 计算 Slater 积分 R^k_{i,u}(r) R_k = slater_integral_radial(r, w, u_i, u_target, k=k) # 占据数归一化:考虑空间简并和自旋简并 # n_i 为子壳层总占据数,需除以 (2l+1) 个 m 态和 2 个自旋态 g_m = 2 * l_occ + 1 # 空间简并度 n_eff = n_i / (g_m * 2.0) # 每个 (m, σ) 的平均占据 # 累加交换贡献 Ku -= n_eff * a_k * R_k * u_i return Ku return apply_exchange
[文档] def exchange_operator_general_spin( r: np.ndarray, w: np.ndarray, l_target: int, spin_target: str, u_occ_by_l_spin: dict[tuple[int, str], list[np.ndarray]], occ_nums_by_l_spin: dict[tuple[int, str], list[float]], cache: SlaterIntegralCache | None = None, ) -> callable: r"""构造自旋分辨的通用交换算子(UHF)。 Parameters ---------- r : np.ndarray 径向网格点 w : np.ndarray 积分权重 l_target : int 目标轨道的角动量量子数 spin_target : str 目标轨道的自旋通道('up' 或 'down') u_occ_by_l_spin : dict[tuple[int, str], list[np.ndarray]] 占据态波函数,按 (l, spin) 索引 例如:{(0, 'up'): [u_1s_up], (0, 'down'): [u_1s_down], ...} occ_nums_by_l_spin : dict[tuple[int, str], list[float]] 占据数,按 (l, spin) 索引 例如:{(0, 'up'): [1.0], (0, 'down'): [1.0], ...} cache : SlaterIntegralCache, optional Slater 积分缓存(可选) Returns ------- callable 交换算子闭包 K[u],接受目标波函数返回交换作用后的结果 Notes ----- UHF 交换仅在**同自旋**占据态间发生: .. math:: K_{\ell,\sigma}[u](r) = -\sum_{\ell'} \sum_{k \in \text{allowed}} a_k(\ell,\ell') \sum_{i \in \sigma} n_i \cdot R^k_{i,u}(r) \cdot u_{\ell',i}(r) 与 RHF 的区别: - 求和仅遍历与目标态同自旋的占据态 - 占据数 n_i 已按自旋分离,无需除以 2 - 保持 m 简并度归一化(除以 2l+1) """ from .slater import slater_integral_radial def apply_exchange(u_target: np.ndarray) -> np.ndarray: """对目标波函数应用交换算子。 Parameters ---------- u_target : np.ndarray 目标径向波函数 Returns ------- np.ndarray 交换作用后的波函数 """ if u_target.shape != r.shape: raise ValueError(f"目标波函数形状 {u_target.shape} 与网格 {r.shape} 不匹配") Ku = np.zeros_like(r) # 仅遍历同自旋的占据态 for (l_occ, spin_occ), u_list in u_occ_by_l_spin.items(): if spin_occ != spin_target: continue # 跳过异自旋态(UHF 交换选择规则) n_list = occ_nums_by_l_spin[(l_occ, spin_occ)] # 获取允许的 k 值 from .angular import allowed_k_values, get_coupling_factor k_values = allowed_k_values(l_target, l_occ) # 遍历允许的多极指标 k for k in k_values: # 获取耦合因子 a_k = get_coupling_factor(l_target, k, l_occ, use_cache=True) if np.abs(a_k) < 1e-15: continue # 跳过零耦合 # 遍历该 (l, spin) 通道的所有占据态 for i, (u_i, n_i) in enumerate(zip(u_list, n_list)): # 计算 Slater 积分 R^k_{i,u}(r) R_k = slater_integral_radial(r, w, u_i, u_target, k=k) # 占据数归一化:仅考虑空间简并度(自旋已分离) g_m = 2 * l_occ + 1 # 空间简并度 n_eff = n_i / g_m # 每个 m 的占据(无需除以 2) # 累加交换贡献 Ku -= n_eff * a_k * R_k * u_i return Ku return apply_exchange