SciPy 1.18 的 LinearOperator 子类报没有 _xp,补上基类初始化
SciPy 1.18.1 的 LinearOperator 需要基类建立数组命名空间。补上初始化或采用公开构造接口,再核对真实矩阵乘积与求解残差。
原来的矩阵算子只设置 shape、dtype 并实现 _matvec,升级 SciPy 1.18 后,eigsh() 还没算出结果就失败:
AttributeError: 'MyLinearOperator' object has no attribute '_xp'
先检查子类自己的 __init__。SciPy 1.18.1 的基类初始化还负责设置数组命名空间 _xp 和维数 ndim;只补两个公开属性,已经不能满足后续 matvec 的需要。旧版文档中“可以调用基类初始化”的措辞,不足以代表 1.18 实际实现的要求。
在子类中调用 super,而不是手工补私有字段
下面是 NumPy 一维向量、实对角算子的定义示例:
import numpy as np
from scipy.sparse.linalg import LinearOperator
class DiagonalOperator(LinearOperator):
def __init__(self, diagonal):
values = np.asarray(diagonal, dtype=np.float64)
if values.ndim != 1 or values.size == 0:
raise ValueError("diagonal must be a nonempty 1-D array")
self._diagonal = values.copy()
super().__init__(dtype=values.dtype, shape=(values.size, values.size))
def _matvec(self, x):
x = np.asarray(x)
if x.ndim != 1 or x.shape[0] != self.shape[1]:
raise ValueError("expected a 1-D vector with matching length")
return self._diagonal * x
示例明确选用 float64 与 NumPy,并在乘法入口拒绝非一维或长度不匹配的向量;它不承接矩阵对象的特殊兼容路径。真实算子应保留自己的数值类型、矩阵尺寸和乘法语义,不能为了让初始化通过,把复数矩阵转换成实数,或把矩形算子改成方阵。super().__init__ 只建立基类状态,不会替你实现正确的乘法。
不要直接写 self._xp = np 来消除报错。它绕过了基类的类型与形状处理,也依赖内部字段;将来另一个方法需要 ndim 等状态时,还可能继续失败。
如果已有子类把 shape、dtype 实现成只读 property,调用基类初始化时可能触发新的赋值错误。这种设计需要一起调整属性实现,或改用公开的构造接口,不能不看子类结构就加一行 super。
不需要继承时,交给公开构造函数
若现有项目已经有可信的矩阵向量乘法函数,也可以直接创建算子:
operator = LinearOperator(
shape=(n, n),
matvec=existing_matvec,
dtype=np.float64,
)
这里的 n 和 existing_matvec 是项目已有的维度与函数,必须满足输入输出形状及 dtype 约定。这一入口会建立内部算子对象,适合并不需要自定义子类状态的场景;它不会自动补齐求解器要求的对称性、伴随乘法或其他数学条件。
先看完整堆栈:若 _xp 缺失出现在另一个类中,或者算子已经正常调用基类,再查包装层是否绕过初始化、恢复旧对象时是否丢状态。不要把每个 AttributeError 都改成同一行。
用乘法结果和原求解任务一起验收
保留修改前的子类和依赖锁定。先用已知向量检查算子乘积与显式矩阵结果是否一致,再运行原来的 eigsh 或其他迭代求解任务,比较残差、特征值与预期约束。对真实项目支持的 dtype、输入维度和伴随路径分别复查;没有异常不代表数学结果正确。
升级还可能带来独立的形状警告。1.18 的列向量 (N, 1) 在 matvec 路径会有弃用提示,不能把它与缺 _xp 合并处理;按实际需要评估 matmat 与输入布局。回退时恢复完整依赖和算子实现组合,避免把仅适配新版内部状态的临时字段留在旧环境里。


