4. 算符与态类型不匹配的运算错误

问题复现:Qobj 算符与错误类型的态对象相乘

在 QuTiP 中,Qobj 是统一的量子对象容器,但同一个 Qobj 类可以承载不同“类型”的对象:算符(operator)、右矢 ket、左矢 bra、超算符(super)等。类型信息并不只写在 type 属性里,更关键的是它由 dims 的结构决定——ket 的 dims 形如 [[2],[1]],bra 形如 [[1],[2]],算符形如 [[2],[2]]。当算符与一个“看起来像态、实际类型不对”的对象做乘法或期望值运算时,QuTiP 会抛出类型/维度相关的错误,而不是给出物理结果。

下面构造一个典型错误场景:一个 2 能级算符 sz,一个用 Qobj 直接包装的列向量(未声明为 ket),以及一个用 Qobj 包装的行向量(未声明为 bra)。

import numpy as np
import qutip as qt

# 正确的算符:dims = [[2],[2]]
sz = qt.sigmaz()
print("sz.type =", sz.type, "| sz.dims =", sz.dims)

# 错误做法 1:用裸 ndarray 列向量构造,dims 被推断为 [[2],[1]] 但 type 仍是 operator
psi_col = qt.Qobj(np.array([[1.0], [0.0]]))
print("psi_col.type =", psi_col.type, "| psi_col.dims =", psi_col.dims)

# 错误做法 2:用裸 ndarray 行向量构造
psi_row = qt.Qobj(np.array([[1.0, 0.0]]))
print("psi_row.type =", psi_row.type, "| psi_row.dims =", psi_row.dims)

# 触发错误:算符 * 非 ket 对象
try:
    bad = sz * psi_col
    print("sz * psi_col =", bad)
except Exception as e:
    print("乘法错误:", type(e).__name__, "->", e)

# 触发错误:对非 ket 对象求期望值
try:
    val = qt.expect(sz, psi_col)
    print("expect =", val)
except Exception as e:
    print("期望值错误:", type(e).__name__, "->", e)

运行后通常会在乘法处得到类似 TypeError: Incompatible quantum object dimensions 或 ValueError: incompatible dimensions 的提示;在 qt.expect 处则可能得到 TypeError: expected a ket or bra 一类信息。核心原因不是数值形状不对,而是 psi_col 的 type 被推断为 operator(因为 Qobj 默认按算符处理二维数组),而 sz * operator 在维度上虽然能乘,但 qt.expect 明确要求 ket/bra 类型。

Qobj 类型由 dims 结构决定:算符 / ket / bra 的区分 算符 operator dims = [[2],[2]] shape = (2, 2) 例:sigmaz() 右矢 ket dims = [[2],[1]] shape = (2, 1) 例:basis(2,0) 左矢 bra dims = [[1],[2]] shape = (1, 2) 例:basis(2,0).dag() 错误构造:Qobj(ndarray) psi_col = Qobj([[1],[0]]) type = operator, dims = [[2],[1]] 形状像 ket,但类型仍是 operator qt.expect(sz, psi_col) → 类型错误 正确构造:声明 ket / bra psi = qt.basis(2, 0) type = ket, dims = [[2],[1]] qt.expect(sz, psi) → 1.0 sz * psi → 合法 ket 结果 直接包装 用 basis / ket2dm 判断依据:先看 type,再看 dims 的左右结构,而不是只看 shape

定位方法:先看 type,再看 dims 的左右结构

排障时不要只盯着 shape。一个 (2,1) 的数组既可能是 ket,也可能被误建为 operator。可靠的检查顺序是:

  1. 打印 obj.type,确认是 'ket'、'bra'、'oper' 还是 'super'。
  2. 打印 obj.dims,看左右两段结构:[[2],[1]] 是 ket,[[1],[2]] 是 bra,[[2],[2]] 是算符。
  3. 若 type 与 dims 的直觉不一致,说明对象是用底层数组直接构造的,需要显式转换。

例如对上面的 psi_col,psi_col.type 会显示 'oper',而 psi_col.dims 是 [[2],[1]]。这种“形状像 ket、类型是算符”的对象正是本案例错误的来源。

修复:转换为正确的 Qobj 形式

根据原始数据的来源,选择对应的转换方式,而不是继续用裸数组硬乘:

原始数据推荐构造/转换得到的 type 与 dims
已知基矢索引qt.basis(2, 0)ket,[[2],[1]]
任意归一化列向量 vqt.Qobj(v, dims=[[2],[1]])ket,[[2],[1]]
已有 ket 对象 psipsi.dag()bra,[[1],[2]]
密度矩阵 rhoqt.ket2dm(psi) 或 qt.Qobj(rho, dims=[[2],[2]])oper,[[2],[2]]
误建的 operator 列向量qt.Qobj(psi_col.full(), dims=[[2],[1]])ket,[[2],[1]]

关键点是显式传入 dims。QuTiP 会根据 dims 的左右结构推断类型:左段为 [2]、右段为 [1] 时识别为 ket;左段为 [1]、右段为 [2] 时识别为 bra。下面给出完整可运行的修复与验证代码:

import numpy as np
import qutip as qt

sz = qt.sigmaz()

# 修复 1:从列向量显式声明为 ket
v = np.array([[1.0], [0.0]])
psi = qt.Qobj(v, dims=[[2], [1]])
print("psi.type =", psi.type, "| psi.dims =", psi.dims)   # ket, [[2],[1]]

# 修复 2:从行向量显式声明为 bra
bra = qt.Qobj(np.array([[1.0, 0.0]]), dims=[[1], [2]])
print("bra.type =", bra.type, "| bra.dims =", bra.dims)   # bra, [[1],[2]]

# 验证乘法与期望值
out = sz * psi
print("sz * psi =", out)
print("expect(sz, psi) =", qt.expect(sz, psi))            # 1.0
print("bra * psi =", bra * psi)                            # 1.0

# 修复 3:把误建的 operator 列向量转回 ket
psi_col = qt.Qobj(np.array([[1.0], [0.0]]))
psi_fixed = qt.Qobj(psi_col.full(), dims=[[2], [1]])
print("psi_fixed.type =", psi_fixed.type)                  # ket
print("expect(sz, psi_fixed) =", qt.expect(sz, psi_fixed)) # 1.0

验证与常见陷阱

  • 验证通过条件:psi.type == 'ket'、bra.type == 'bra',且 qt.expect(sz, psi) 返回 1.0(对 |0⟩ 态),bra * psi 返回 1.0。
  • 陷阱一:只改 shape 不改 dims。用 reshape 得到的仍是 operator,必须重新用 Qobj(..., dims=...) 包装。
  • 陷阱二:对密度矩阵用 qt.expect(sz, rho) 是合法的(QuTiP 支持对 oper 求期望值),但对误建的 operator 列向量不合法,因为它的 dims 右段是 [1],不是方阵结构。
  • 陷阱三:多比特系统中 dims 要写成嵌套列表,如 [[2,2],[1,1]] 表示两比特 ket,不能只写 [[4],[1]],否则后续张量运算会再次出现类型/维度问题。
排障口诀:先 type 后 dims,形状相同不代表类型相同;构造态对象时显式写 dims,比事后猜测更省时间。