atomscf.operator 源代码

from __future__ import annotations

import numpy as np
from scipy.linalg import eigh
from scipy.sparse.linalg import eigsh

from .grid import trapezoid_weights
from .utils import normalize_radial_u

"""径向薛定谔方程求解器模块。

本模块提供多种求解径向薛定谔方程束缚态的求解器,适用于不同的网格类型和精度需求。

求解器选择指南
==============

本模块提供以下四种求解器,按推荐优先级排序:

1. **solve_bound_states_transformed** (变量变换方法)
   - 适用网格:指数网格(需配合 radial_grid_exp_transformed 使用)
   - 精度:最高(相比 FD 方法提升 ~7 倍)
   - 速度:快(相比 FD5-aux 提升 ~4 倍)
   - 限制:仅适用于特定网格类型,需要提供 delta 和 Rp 参数
   - 推荐场景:生产环境计算,需要高精度和高效率时

2. **solve_bound_states_fd5_auxlinear** (插值到均匀网格 + 5点有限差分)
   - 适用网格:任意非均匀网格
   - 精度:高(5 点差分格式)
   - 速度:中等(需要插值开销)
   - 限制:插值会引入额外误差
   - 推荐场景:使用非均匀网格但需要较高精度时的通用方案

3. **solve_bound_states_fd5** (直接 5点有限差分)
   - 适用网格:线性等距网格
   - 精度:高(5 点差分格式)
   - 速度:快(无插值开销)
   - 限制:仅限均匀网格
   - 推荐场景:使用均匀网格时的高效选择

4. **solve_bound_states_fd** (基础有限差分)
   - 适用网格:任意网格(自动检测并适配)
   - 精度:基础(2 点差分格式)
   - 速度:快
   - 限制:精度较低
   - 推荐场景:快速原型验证,或作为其他方法的参考基准

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

使用变量变换方法(推荐)::

    from atomscf.grid import radial_grid_exp_transformed
    from atomscf.operator import solve_bound_states_transformed

    # 生成指数网格
    r, w, delta, Rp = radial_grid_exp_transformed(n=800, rmin=1e-6, rmax=50.0)

    # 求解
    eps, U = solve_bound_states_transformed(r, l=0, v_of_r=V_eff,
                                            delta=delta, Rp=Rp, k=3)

使用通用 FD5-aux 方法::

    from atomscf.grid import radial_grid_log
    from atomscf.operator import solve_bound_states_fd5_auxlinear

    # 生成对数网格
    r, w = radial_grid_log(n=800, rmin=1e-6, rmax=50.0)

    # 求解(自动插值到均匀网格)
    eps, U = solve_bound_states_fd5_auxlinear(r, l=0, v_of_r=V_eff, k=3)

注意事项
========

- 所有求解器返回归一化的径向波函数 u(r),满足 ∫ |u(r)|² dr = 1
- 能量按升序排列(束缚态从浅到深)
- 对于高激发态,可能需要调整网格参数或能量搜索范围
"""

__all__ = [
    "radial_hamiltonian_matrix",
    "solve_bound_states_fd",
    "radial_hamiltonian_matrix_linear_fd5",
    "solve_bound_states_fd5",
    "solve_bound_states_fd5_auxlinear",
    "solve_bound_states_transformed",
    "build_transformed_hamiltonian",  # 新增:HF 复用
]


def _second_derivative_tridiag_nonuniform(r: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    r"""构造非均匀网格上二阶导数算子的三对角表示(内部点)。

    对于非均匀一维网格 :math:`(r_0,\dots,r_{N-1})`,在内部点 :math:`i=1,\dots,N-2` 上,
    二阶导数可用以下一致格式近似:

    .. math::
        \left.\frac{d^2u}{dr^2}\right|_{r_i}
        \approx \frac{2}{h_{i-1}+h_i}\left[\frac{u_{i+1}-u_i}{h_i} - \frac{u_i-u_{i-1}}{h_{i-1}}\right],

    其中 :math:`h_{i-1}=r_i-r_{i-1},\ h_i=r_{i+1}-r_i`。

    本函数返回内部点上的三对角系数数组 :math:`(a, b, c)`,对应 :math:`u_{i-1}, u_i, u_{i+1}` 的系数,
    以及内部坐标数组 :math:`r_{\text{inner}}=(r_1,\dots,r_{N-2})`。

    Parameters
    ----------
    r : numpy.ndarray
        单调递增的一维网格坐标。

    Returns
    -------
    a : numpy.ndarray
        下对角系数数组,长度 :math:`N-2`,首元素对物理上无意义(可忽略或视为填充)。
    b : numpy.ndarray
        对角系数数组,长度 :math:`N-2`。
    c : numpy.ndarray
        上对角系数数组,长度 :math:`N-2`,末元素对物理上无意义(可忽略或视为填充)。
    r_inner : numpy.ndarray
        内部网格坐标 :math:`(r_1,\dots,r_{N-2})`。

    Notes
    -----
    - 该离散对应 Dirichlet 边界条件 :math:`u(r_0)=u(r_{N-1})=0`,因此仅对内部点建立方程。
    - 返回系数对应算子 :math:`\frac{d^2}{dr^2}`,后续 Hamiltonian 需乘以 :math:`-\tfrac{1}{2}`。
    """
    if r.ndim != 1:
        raise ValueError("r 必须是一维数组")
    if np.any(np.diff(r) <= 0):
        raise ValueError("r 必须严格单调递增")
    n = r.size
    if n < 3:
        raise ValueError("至少需要 3 个网格点以建立内部二阶导数")

    r_inner = r[1:-1]
    h_left = np.diff(r[:-1])  # r[i] - r[i-1], len = n-2 (for i=1..n-2)
    h_right = np.diff(r[1:])  # r[i+1] - r[i], len = n-2 (for i=1..n-2)

    a = np.empty(n - 2, dtype=float)
    b = np.empty(n - 2, dtype=float)
    c = np.empty(n - 2, dtype=float)

    denom = h_left + h_right
    a[:] = 2.0 / (h_left * denom)
    c[:] = 2.0 / (h_right * denom)
    b[:] = -2.0 / (h_left * h_right)

    return a, b, c, r_inner


[文档] def radial_hamiltonian_matrix( r: np.ndarray, l: int, v_of_r: np.ndarray, ) -> tuple[np.ndarray, np.ndarray]: r"""构造径向 Hamiltonian(内部点)并返回矩阵与内部网格坐标。 径向方程(原子单位)在 :math:`u(r)` 表示下为: .. math:: \left[-\frac{1}{2}\frac{d^2}{dr^2}+\frac{\ell(\ell+1)}{2r^2}+v(r)\right]u(r)=\varepsilon\,u(r), 本函数在非均匀网格上采用二阶差分对 :math:`\tfrac{d^2}{dr^2}` 离散,并以 Dirichlet 边界 :math:`u(r_0)=u(r_{N-1})=0` 构造内部点的对称三对角矩阵。 Parameters ---------- r : numpy.ndarray 单调递增的径向网格 :math:`(r_0,\dots,r_{N-1})`。 l : int 角动量量子数 :math:`\ell`。 v_of_r : numpy.ndarray 势能数组 :math:`v(r_i)`,长度与 :data:`r` 一致。 Returns ------- H : numpy.ndarray 内部点上的 Hamiltonian 矩阵,形状 :math:`(N-2, N-2)`。 r_inner : numpy.ndarray 内部网格坐标 :math:`(r_1,\dots,r_{N-2})`。 Notes ----- - 矩阵是对称实矩阵,适用于标准本征求解器。 - 若需得到完整长度的 :math:`u(r)`,可在内部解向量两端补零以满足 Dirichlet 边界。 """ if v_of_r.shape != r.shape: raise ValueError("v_of_r 的形状必须与 r 相同") if l < 0: raise ValueError("l 必须为非负整数") a, b, c, r_inner = _second_derivative_tridiag_nonuniform(r) n_in = r_inner.size # 组装二阶导数三对角矩阵 D2 D2 = np.zeros((n_in, n_in), dtype=float) # 对角 np.fill_diagonal(D2, b) # 上/下对角 idx = np.arange(n_in - 1) D2[idx, idx + 1] = c[:-1] D2[idx + 1, idx] = a[1:] # 动能算子:T = -1/2 D2 T = -0.5 * D2 # 势能 + 离心项(内部点) v_inner = v_of_r[1:-1] lterm = 0.5 * l * (l + 1) / (r_inner**2) V = np.diag(v_inner + lterm) H = T + V return H, r_inner
[文档] def solve_bound_states_fd( r: np.ndarray, l: int, v_of_r: np.ndarray, k: int = 4, ) -> tuple[np.ndarray, np.ndarray]: r"""基于有限差分 Hamiltonian 的径向束缚态求解(取低端 :math:`k` 个本征对)。 该方法将径向 Hamiltonian 离散为内部点的对称矩阵,调用密集线性代数本征求解。 适合教学验证(例如氢样势下的 1s 能级),在网格较大时可能较慢。 Parameters ---------- r : numpy.ndarray 单调递增的径向网格 :math:`(r_0,\dots,r_{N-1})`。 l : int 角动量量子数 :math:`\ell`。 v_of_r : numpy.ndarray 势能数组 :math:`v(r_i)`,长度与 :data:`r` 一致。 k : int, optional 返回最低能的本征态个数(默认 4)。 Returns ------- eps : numpy.ndarray 低端 :math:`k` 个本征值(按升序)。 U : numpy.ndarray 对应的径向函数矩阵 :math:`U`,形状 :math:`(k, N)`,已在 :math:`[r_0,r_{N-1}]` 上按 :math:`\int u^2\,dr=1` 归一,并在两端补零以满足 Dirichlet 边界。 Notes ----- - 实际内部计算在 :math:`(r_1,\dots,r_{N-2})` 上进行,端点边界条件 :math:`u=0`。 - 若只需氢样势 1s 态,建议选择较大的 :math:`r_\max`(如 50–100)与足够细的网格以降低边界误差。 - 使用 scipy.linalg.eigh 的 subset_by_index 只求前 k 个本征值以提升性能。 """ H, r_inner = radial_hamiltonian_matrix(r, l, v_of_r) k_actual = min(k, H.shape[0]) # 只求前 k_actual 个最低本征值(参考 dftatom 和 tinydft) e_vals, U_inner = eigh(H, subset_by_index=(0, k_actual - 1)) eps = e_vals U_out = np.zeros((k_actual, r.size), dtype=float) w = trapezoid_weights(r) for j in range(k_actual): u = np.zeros_like(r) u[1:-1] = U_inner[:, j] u, _ = normalize_radial_u(u, r, w) U_out[j] = u return eps, U_out
def _is_uniform_linear_grid(r: np.ndarray, tol: float = 1e-12) -> tuple[bool, float]: """判断是否为等间距线性网格,并返回步长。 Parameters ---------- r : numpy.ndarray 径向网格。 tol : float 判断等间距的容差。 Returns ------- is_uniform : bool 是否等间距。 h : float 网格步长(若非等间距则返回 0)。 """ dr = np.diff(r) if np.allclose(dr, dr[0], atol=tol, rtol=0): return True, float(dr[0]) return False, 0.0
[文档] def radial_hamiltonian_matrix_linear_fd5( r: np.ndarray, l: int, v_of_r: np.ndarray, ) -> tuple[np.ndarray, np.ndarray]: r"""在等间距线性网格上构造五点有限差分的径向 Hamiltonian。 使用五点中心差分格式近似二阶导数: .. math:: \frac{d^2 u}{dr^2}\bigg|_{r_i} \approx \frac{-u_{i+2} + 16u_{i+1} - 30u_i + 16u_{i-1} - u_{i-2}}{12h^2}. 精度::math:`\mathcal{O}(h^4)`,在等间距网格上比二点/三点差分精度更高。 Parameters ---------- r : numpy.ndarray **必须为等间距** 线性网格 :math:`(r_0,\dots,r_{N-1})`,否则抛出异常。 l : int 角动量量子数 :math:`\ell`。 v_of_r : numpy.ndarray 势能数组 :math:`v(r_i)`,长度与 :data:`r` 一致。 Returns ------- H : numpy.ndarray 内部点上的 Hamiltonian 矩阵,形状 :math:`(N-4, N-4)`。由于五点模板需要两侧各两点, 实际对应网格索引 :math:`i=2,\dots,N-3`,即去掉首尾各两个点。 r_inner : numpy.ndarray 内部网格坐标 :math:`(r_2,\dots,r_{N-3})`。 """ is_uniform, h = _is_uniform_linear_grid(r) if not is_uniform: raise ValueError("radial_hamiltonian_matrix_linear_fd5 要求 r 为等间距网格,请考虑插值或使用二阶 FD") if l < 0: raise ValueError("l 必须为非负整数") if v_of_r.shape != r.shape: raise ValueError("v_of_r 的形状必须与 r 相同") n = r.size if n < 5: raise ValueError("五点差分至少需要 5 个网格点") r_inner = r[2:-2] n_in = r_inner.size h2 = h * h # 五点差分系数(标准化到步长 h=1) # -u_{i-2} + 16*u_{i-1} - 30*u_i + 16*u_{i+1} - u_{i+2} # 对应二阶导数的 12 倍(除以 12h^2 后得到真实二阶导数) coef_main = -30.0 / (12.0 * h2) coef_near = 16.0 / (12.0 * h2) coef_far = -1.0 / (12.0 * h2) # 构造五对角矩阵(动能项) T = np.zeros((n_in, n_in), dtype=float) idx = np.arange(n_in) # 主对角 np.fill_diagonal(T, coef_main) # ±1 对角 T[idx[:-1], idx[:-1] + 1] = coef_near T[idx[:-1] + 1, idx[:-1]] = coef_near # ±2 对角 T[idx[:-2], idx[:-2] + 2] = coef_far T[idx[:-2] + 2, idx[:-2]] = coef_far T *= -0.5 # 势能 + 离心项(内部点) v_inner = v_of_r[2:-2] lterm = 0.5 * l * (l + 1) / (r_inner**2) V = np.diag(v_inner + lterm) H = T + V return H, r_inner
[文档] def solve_bound_states_fd5( r: np.ndarray, l: int, v_of_r: np.ndarray, k: int = 4, ) -> tuple[np.ndarray, np.ndarray]: r"""在等间距线性网格上使用五点有限差分求解径向束缚态(取低端 :math:`k` 个本征对)。 相比二阶差分(`solve_bound_states_fd`),五点格式精度更高(:math:`\mathcal{O}(h^4)`), 但要求网格**必须为等间距**,且去掉首尾各两个点以满足五点模板要求。 Parameters ---------- r : numpy.ndarray **必须为等间距** 线性网格 :math:`(r_0,\dots,r_{N-1})`。 l : int 角动量量子数 :math:`\ell`。 v_of_r : numpy.ndarray 势能数组 :math:`v(r_i)`,长度与 :data:`r` 一致。 k : int, optional 返回最低能的本征态个数(默认 4)。 Returns ------- eps : numpy.ndarray 低端 :math:`k` 个本征值(按升序)。 U : numpy.ndarray 对应的径向函数矩阵 :math:`U`,形状 :math:`(k, N)`,已在 :math:`[r_0,r_{N-1}]` 上按 :math:`\int u^2\,dr=1` 归一,并在两端补零(首尾各两个点为 0)。 Notes ----- - 实际内部计算在 :math:`(r_2,\dots,r_{N-3})` 上进行,首尾各两点作为边界条件设为 0。 - 使用 scipy.linalg.eigh 的 subset_by_index 只求前 k 个本征值以提升性能。 """ H, r_inner = radial_hamiltonian_matrix_linear_fd5(r, l, v_of_r) k_actual = min(k, H.shape[0]) # 只求前 k_actual 个最低本征值 e_vals, U_inner = eigh(H, subset_by_index=(0, k_actual - 1)) eps = e_vals U_out = np.zeros((k_actual, r.size), dtype=float) w = trapezoid_weights(r) for j in range(k_actual): u = np.zeros_like(r) u[2:-2] = U_inner[:, j] # 首尾各两个点为 0 u, _ = normalize_radial_u(u, r, w) U_out[j] = u return eps, U_out
[文档] def solve_bound_states_fd5_auxlinear( r: np.ndarray, l: int, v_of_r: np.ndarray, k: int = 4, n_aux: int | None = None, ) -> tuple[np.ndarray, np.ndarray]: r"""在非等间距网格上,使用辅助等间距线性网格(FD5)求解本征,再插值回原网格。 步骤: 1) 构造线性等间距网格 :math:`r_{\text{lin}} \in [r_0, r_{N-1}]`,点数 :math:`n_{\text{aux}}`(默认与原网格同级别但限制上限2500以平衡精度和速度)。 2) 将势 :math:`v(r)` 插值到线性网格上; 3) 用五点格式 `solve_bound_states_fd5` 在线性网格上求低端本征; 4) 将解插值回原网格,并在原网格上归一化(`∫ u^2 dr = 1`)。 性能优化:使用 scipy.linalg.eigh subset_by_index 只求前 k 个本征值,即使 n_aux 较大也能快速求解。 """ if n_aux is None: n_aux = min(max(len(r), 1000), 2500) # 提高上限以减少插值误差 rmin, rmax = float(r[0]), float(r[-1]) r_lin = np.linspace(rmin, rmax, n_aux) v_lin = np.interp(r_lin, r, v_of_r) eps, U_lin = solve_bound_states_fd5(r_lin, l=l, v_of_r=v_lin, k=k) U_out = np.zeros((len(eps), r.size), dtype=float) w = trapezoid_weights(r) for j in range(len(eps)): u_lin = U_lin[j] u = np.interp(r, r_lin, u_lin) u, _ = normalize_radial_u(u, r, w) U_out[j] = u return eps, U_out
[文档] def solve_bound_states_transformed( r: np.ndarray, l: int, v_of_r: np.ndarray, delta: float, Rp: float, k: int = 4, use_sparse: bool = True, ) -> tuple[np.ndarray, np.ndarray]: r"""使用变量变换方法求解径向束缚态(适用于指数网格)。 方法基于文献 [1]_: 网格: :math:`r(j) = R_p(\exp(j\delta) - 1) + r_{\min}, \quad j=0,1,\ldots,j_{\max}` 变量变换: :math:`u(j) = v(j) \exp(j\delta/2)` 变换后的方程(没有一阶导数项): :math:`v''(j) - \frac{\delta^2}{4}v(j) = 2R_p^2\delta^2 \exp(2j\delta)(E - V_{\text{eff}}(r(j)))v(j)` 有限差分离散: :math:`v(j+1) - 2v(j) + v(j-1) - \frac{\delta^2}{4}v(j) = 2R_p^2\delta^2 \exp(2j\delta)(E - V_{\text{eff}}(j))v(j)` 广义特征值问题: :math:`H v = E B v` 其中: - :math:`H[i,i] = 2 + \frac{\delta^2}{4} + \text{exp\_factor} \cdot V_{\text{eff}}(i+1)` - :math:`H[i,i\pm1] = -1` - :math:`B[i,i] = 2\delta^2 R_p^2 \exp(2\delta(i+1))` **关键处理**:去掉 j=0 点避免奇异性(参考 [1]_ 第54行) Parameters ---------- r : numpy.ndarray 指数网格,由 `radial_grid_exp_transformed` 生成,满足 r[j] = Rp*(exp(j*delta)-1) + rmin。 l : int 角动量量子数。 v_of_r : numpy.ndarray 势能数组(包含核势能),长度与 r 一致。 delta : float 网格参数 δ(从 `radial_grid_exp_transformed` 返回)。 Rp : float 网格参数 R_p(从 `radial_grid_exp_transformed` 返回)。 k : int, optional 返回最低能的本征态个数(默认 4)。 use_sparse : bool, optional 是否使用稀疏矩阵求解器(默认 True,适用于大型矩阵)。 Returns ------- eps : numpy.ndarray 低端 k 个本征值(按升序)。 U : numpy.ndarray 对应的径向波函数矩阵,形状 (k, N),已归一化。 Notes ----- - 去掉 j=0 点后,实际求解的网格索引为 j=1,2,...,j_max - v[0] 在边界条件下为 0(不参与求解) - 求解后变换回 u = v * exp(j*delta/2) 并归一化 References ---------- .. [ExpGridTransform] 指数网格变量变换方法 来源:Computational Physics Fall 2024, Assignment 7, Problem 2 https://github.com/bud-primordium/Computational-Physics-Fall-2024/tree/main/Assignment_7/Problem_2 problem_2.tex 第155-235行(矩阵元推导) """ if v_of_r.shape != r.shape: raise ValueError("v_of_r 的形状必须与 r 相同") if l < 0: raise ValueError("l 必须为非负整数") n = r.size if n < 3: raise ValueError("至少需要 3 个网格点") # 去掉 j=0 点(近核奇异) # 实际求解的索引:j_actual = 1, 2, ..., n-1 对应矩阵索引 i = 0, 1, ..., n-2 n_reduced = n - 1 # 计算有效势能(包含离心项) V_eff = v_of_r + 0.5 * l * (l + 1) / (r**2 + 1e-30) # 构造 Hamiltonian 矩阵 H 和质量矩阵 B # 参考 problem_2.tex 第155-183行 # 规模阈值:实际禁用稀疏(密集+白化已足够快) # 稀疏求解器(ARPACK eigsh)对该问题收敛极慢,即使白化后仍不如密集 use_sparse_actual = use_sparse and (n_reduced > 10000) if use_sparse_actual: from scipy.sparse import diags from scipy.sparse.linalg import eigsh from scipy.sparse import csr_matrix import numpy as _np import os import time as _time debug_solver = os.environ.get("ATOMSCF_DEBUG_SOLVER", "0") == "1" # 组装 H 的对角(三对角 Off=-1) diag_H = _np.zeros(n_reduced) for i in range(n_reduced): j_actual = i + 1 # 实际网格索引(跳过 j=0) exp_factor = 2.0 * delta**2 * Rp**2 * _np.exp(2.0 * delta * j_actual) diag_H[i] = 2.0 + delta**2 / 4.0 + exp_factor * V_eff[j_actual] # B 的对角 diag_B = _np.zeros(n_reduced) for i in range(n_reduced): j_actual = i + 1 diag_B[i] = 2.0 * delta**2 * Rp**2 * _np.exp(2.0 * delta * j_actual) # 标准化白化:C = B^{-1/2} H B^{-1/2}(保持三对角) inv_sqrt_B = 1.0 / _np.sqrt(_np.maximum(diag_B, 1e-300)) # C 主对角 diag_C = diag_H * (inv_sqrt_B ** 2) # C 上/下对角:-inv[i]*inv[i+1] off_C = -inv_sqrt_B[:-1] * inv_sqrt_B[1:] C = diags([off_C, diag_C, off_C], offsets=[-1, 0, 1], shape=(n_reduced, n_reduced), format="csr") # eigsh 参数(放宽以提高收敛性) # 对于 n>2000 的大网格,优先保证收敛而非极致精度 k_actual = min(k, n_reduced) # ncv 需要足够大:至少 4k+10,对大矩阵用 min(8k, 100) ncv = min(max(8 * k_actual, 60), n_reduced - 1, 150) tol = 1e-7 # 放宽容差 maxiter = 3000 # 允许更多迭代 if debug_solver: print(f" [DEBUG] l={l}, n_reduced={n_reduced}, k={k_actual}, ncv={ncv}, 开始 eigsh...") t0 = _time.time() try: e_vals, Y = eigsh(C, k=k_actual, which="SA", ncv=ncv, tol=tol, maxiter=maxiter) if debug_solver: print(f" [DEBUG] eigsh 成功,用时 {_time.time()-t0:.4f}s") except Exception as e1: # 重试:进一步放宽参数 if debug_solver: print(f" [DEBUG] 首次 eigsh 失败: {e1}, 尝试重试...") try: ncv2 = min(max(10 * k_actual, 100), n_reduced - 1, 200) if debug_solver: t1 = _time.time() e_vals, Y = eigsh(C, k=k_actual, which="SA", ncv=ncv2, tol=1e-6, maxiter=5000) if debug_solver: print(f" [DEBUG] 重试成功(ncv={ncv2}),用时 {_time.time()-t1:.4f}s") except Exception as e2: # 稀疏失败:回退到密集广义特征值 if debug_solver: print(f" [DEBUG] 重试失败: {e2}, 回退到密集求解...") t2 = _time.time() H = _np.zeros((n_reduced, n_reduced), dtype=float) B = _np.zeros((n_reduced, n_reduced), dtype=float) for i in range(n_reduced): if i > 0: H[i, i - 1] = -1.0 H[i, i] = diag_H[i] if i < n_reduced - 1: H[i, i + 1] = -1.0 B[i, i] = diag_B[i] from scipy.linalg import eigh as _eigh e_vals, V_inner = _eigh(H, B, subset_by_index=(0, k_actual - 1)) if debug_solver: print(f" [DEBUG] 密集求解成功,用时 {_time.time()-t2:.4f}s") # 归一化到 v 空间(密集回退已经是 v) Y = V_inner # 将 y 变回 v:v = B^{-1/2} y if Y.ndim == 1: Y = Y[:, None] V_inner = (inv_sqrt_B[:, None] * Y) else: # 密集矩阵求解(也使用白化避免病态) import numpy as _np # 组装 diag_H 与 diag_B diag_H = _np.zeros(n_reduced) diag_B = _np.zeros(n_reduced) for i in range(n_reduced): j_actual = i + 1 exp_factor = 2.0 * delta**2 * Rp**2 * _np.exp(2.0 * delta * j_actual) diag_H[i] = 2.0 + delta**2 / 4.0 + exp_factor * V_eff[j_actual] diag_B[i] = exp_factor # 白化:C = B^{-1/2} H B^{-1/2}(保持三对角) inv_sqrt_B = 1.0 / _np.sqrt(_np.maximum(diag_B, 1e-300)) diag_C = diag_H * (inv_sqrt_B ** 2) off_C = -inv_sqrt_B[:-1] * inv_sqrt_B[1:] # 构建密集三对角矩阵 C C = _np.zeros((n_reduced, n_reduced), dtype=float) for i in range(n_reduced): if i > 0: C[i, i-1] = off_C[i-1] C[i, i] = diag_C[i] if i < n_reduced - 1: C[i, i+1] = off_C[i] # 求解标准特征值问题 C y = E y k_actual = min(k, n_reduced) from scipy.linalg import eigh as _eigh e_vals, Y = _eigh(C, subset_by_index=(0, k_actual - 1)) # 将 y 变回 v:v = B^{-1/2} y if Y.ndim == 1: Y = Y[:, None] V_inner = (inv_sqrt_B[:, None] * Y) # 变换回 u = v * exp(j*delta/2) eps = e_vals U_out = np.zeros((k_actual, r.size), dtype=float) w = trapezoid_weights(r) for idx in range(k_actual): v_reduced = V_inner[:, idx] # 长度 n_reduced # 还原完整的 v(j=0 处为 0) v_full = np.zeros(n) v_full[1:] = v_reduced # v[0] = 0, v[1:] = v_reduced # 变换回 u = v * exp(j*delta/2) j = np.arange(n) u = v_full * np.exp(j * delta / 2.0) # 归一化 u, _ = normalize_radial_u(u, r, w) U_out[idx] = u return eps, U_out
[文档] def build_transformed_hamiltonian( r: np.ndarray, l: int, v_of_r: np.ndarray, delta: float, Rp: float, ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: r"""构造变量变换的 Hamiltonian 和质量矩阵(HF 复用)。 返回广义特征值问题的矩阵形式(但不求解): H v = E B v 其中 v 是变换后的波函数。 Parameters ---------- r : np.ndarray 指数变换网格 l : int 角动量量子数 v_of_r : np.ndarray 势能(含外势 + Hartree) delta : float 网格参数 δ Rp : float 网格参数 R_p Returns ------- H : np.ndarray Hamiltonian 矩阵(去掉 j=0 后的尺寸 n-1 × n-1) B : np.ndarray 质量矩阵(对角,尺寸 n-1 × n-1) r_inner : np.ndarray 内部网格点(去掉 j=0,长度 n-1) Notes ----- 该函数抽取自 solve_bound_states_transformed, 用于 HF 中构造局域 Hamiltonian 部分。 交换矩阵 K 仍在原始 u(r) 空间构造, 需要在 v 和 u 之间转换:u(j) = v(j) * exp(j*delta/2) """ if v_of_r.shape != r.shape: raise ValueError("v_of_r 的形状必须与 r 相同") if l < 0: raise ValueError("l 必须为非负整数") n = r.size if n < 3: raise ValueError("至少需要 3 个网格点") # 去掉 j=0 点(近核奇异) n_reduced = n - 1 r_inner = r[1:] # j=1, 2, ..., n-1 # 有效势能 V_eff = v_of_r + 0.5 * l * (l + 1) / (r**2 + 1e-30) # 构造 H 矩阵(三对角) H = np.zeros((n_reduced, n_reduced), dtype=float) B = np.zeros((n_reduced, n_reduced), dtype=float) for i in range(n_reduced): j_actual = i + 1 # 实际网格索引 exp_factor = 2.0 * delta**2 * Rp**2 * np.exp(2.0 * delta * j_actual) # H 矩阵元 if i > 0: H[i, i - 1] = -1.0 H[i, i] = 2.0 + delta**2 / 4.0 + exp_factor * V_eff[j_actual] if i < n_reduced - 1: H[i, i + 1] = -1.0 # B 矩阵(对角) B[i, i] = 2.0 * delta**2 * Rp**2 * np.exp(2.0 * delta * j_actual) return H, B, r_inner