第三章 QuTiP核心对象:Qobj的创建与属性
各位同学,今天我们来聊聊QuTiP里最核心的东西——Qobj。说白了,它就是量子对象的缩写,是QuTiP用来描述量子态、算符、密度矩阵的统一数据结构。我刚开始用QuTiP的时候,觉得这东西不就是个矩阵吗?后来踩了几个坑才发现,它远不止这么简单。
3.1 什么是Qobj?
Qobj本质上是一个封装好的矩阵,但它比普通矩阵多了很多量子力学的“灵魂”。它知道自己是作用在哪个希尔伯特空间上的,也知道自己是态矢量还是算符。嗯,这一点在实际仿真中特别重要。
我个人习惯把Qobj理解成一个“智能矩阵”。它不光存数据,还存了维度信息、类型信息。你想想看,在量子光学里,我们经常要处理多个模式、多个能级,如果光靠手动管理维度,那代码写起来得多痛苦?
核心要点:Qobj = 矩阵 + 维度信息 + 类型标签
3.2 创建Qobj的几种方式
创建Qobj其实很简单,最直接的方法就是传入一个NumPy数组或列表。我在项目中遇到过一个问题:刚开始总是忘记指定维度,结果算出来的东西完全不对。所以这里我建议大家养成好习惯,创建时就明确维度。
3.2.1 从列表或数组创建
import numpy as np
import qutip as qt
# 最简单的创建方式
psi = qt.Qobj([[1], [0]]) # 一个二能级系统的基态
print(psi)
# 指定维度
psi_dims = qt.Qobj([[1], [0]], dims=[[2], [1]])
print(psi_dims)
这里要注意,dims参数是个列表的列表。第一个列表表示“行”的维度,第二个列表表示“列”的维度。对于态矢量,列维度永远是1。
3.2.2 使用内置函数创建
QuTiP提供了很多便捷函数,省得我们手动敲矩阵。我建议你优先用这些函数,既快又不容易出错。
| 函数 | 功能 | 示例 |
|---|---|---|
qt.basis(N, i) |
创建N维希尔伯特空间的第i个基矢 | qt.basis(3, 1) 创建|1⟩态 |
qt.fock(N, n) |
创建N维Fock空间的n光子态 | qt.fock(5, 2) 创建|2⟩态 |
qt.coherent(N, alpha) |
创建相干态 | qt.coherent(10, 1.5) |
qt.qeye(N) |
创建N维单位算符 | qt.qeye(3) |
qt.destroy(N) |
创建N维湮灭算符 | qt.destroy(4) |
小技巧:创建相干态时,N要选得足够大,一般取光子数期望值的3-5倍。我曾经取太小,结果相干态被截断得面目全非,仿真结果完全不能用。
3.3 Qobj的核心属性
创建好Qobj后,我们可以查看它的各种属性。这些属性就像是量子对象的“身份证”,告诉你它到底是什么。
psi = qt.basis(3, 1) # 三能级系统的第一激发态
print("数据矩阵:\n", psi.data)
print("维度信息:", psi.dims)
print("形状:", psi.shape)
print("是否是厄米算符:", psi.isherm)
print("是否是单位算符:", psi.isunit)
print("类型:", psi.type)
运行结果会告诉你:dims是[[3], [1]],type是'ket'(态矢量)。如果是密度矩阵,type会显示为'oper'或'dm'。
这里有个坑我要提醒你:type属性是根据dims自动推断的,不是手动设置的。如果你创建了一个2×2的矩阵,但dims设置成[[2], [2]],那它会被当成算符而不是态矢量。
避坑指南:我曾经在写多模系统时,把两个模式的维度搞反了,结果type显示为'oper',但我明明想创建的是态矢量。检查了半天才发现是dims顺序错了。所以创建后第一件事就是打印dims和type确认一下。
3.4 态矢量与密度矩阵
在量子光学里,我们经常要在纯态和混合态之间切换。纯态用态矢量表示,混合态用密度矩阵表示。QuTiP让这种切换变得非常方便。
3.4.1 态矢量转密度矩阵
# 创建一个纯态
psi = (qt.basis(2, 0) + qt.basis(2, 1)).unit() # 归一化的|+⟩态
# 转成密度矩阵
rho = qt.ket2dm(psi)
print("密度矩阵:\n", rho)
print("纯度:", (rho * rho).tr()) # 纯态纯度=1
这里unit()方法是对态矢量做归一化。我建议你养成习惯,创建态后先归一化,不然很多运算结果会莫名其妙。
3.4.2 密度矩阵的迹和纯度
密度矩阵的迹永远是1(对于合法量子态),纯度则告诉我们这个态有多“纯”。纯态的纯度是1,混合态小于1。
# 创建一个混合态
rho_mixed = 0.5 * qt.ket2dm(qt.basis(2, 0)) + 0.5 * qt.ket2dm(qt.basis(2, 1))
print("混合态纯度:", (rho_mixed * rho_mixed).tr()) # 应该是0.5
关键点:密度矩阵的迹为1是物理上可接受的必要条件。如果计算过程中迹偏离了1,说明数值误差累积了,需要重新归一化。
3.5 基本运算与操作
QuTiP的Qobj支持很多直观的运算,这让量子力学的计算变得像普通代数一样自然。
3.5.1 加减乘除与内积
psi0 = qt.basis(2, 0)
psi1 = qt.basis(2, 1)
# 叠加态
psi_super = (psi0 + psi1).unit()
# 内积(重叠积分)
overlap = psi0.dag() * psi1 # 结果是0,因为正交
print("⟨0|1⟩ =", overlap)
# 算符作用在态上
a = qt.destroy(5)
psi_fock = qt.fock(5, 3)
a_psi = a * psi_fock # 湮灭算符作用,得到√3|2⟩
print("a|3⟩ =", a_psi)
这里dag()方法返回厄米共轭,也就是转置共轭。对于态矢量,psi.dag()就是左矢⟨ψ|。
3.5.2 张量积
多模系统是量子光学的家常便饭。张量积用&运算符,非常直观。
# 两个单模态的张量积
psi_mode1 = qt.basis(3, 1) # 模式1的|1⟩态
psi_mode2 = qt.basis(3, 0) # 模式2的|0⟩态
psi_two_modes = qt.tensor(psi_mode1, psi_mode2) # 或者用 psi_mode1 & psi_mode2
print("双模态维度:", psi_two_modes.dims) # [[3, 3], [1, 1]]
我个人习惯用qt.tensor()函数,因为可读性更好。但&运算符写起来更简洁,看个人喜好。
3.5.3 部分迹
当我们只关心多模系统中的某个模式时,就需要用到部分迹。这在研究纠缠和退相干时特别有用。
# 创建一个纠缠态
psi_entangled = (qt.basis(2, 0) & qt.basis(2, 0) + qt.basis(2, 1) & qt.basis(2, 1)).unit()
rho_entangled = qt.ket2dm(psi_entangled)
# 对模式2求部分迹,得到模式1的约化密度矩阵
rho_mode1 = qt.ptrace(rho_entangled, 0) # 0表示保留模式1
print("模式1的约化密度矩阵:\n", rho_mode1)
print("纯度:", (rho_mode1 * rho_mode1).tr()) # 纠缠态下纯度小于1
经验之谈:部分迹是诊断纠缠的好工具。如果约化密度矩阵的纯度小于1,说明两个模式之间存在纠缠。我在做量子密钥分发仿真时,就靠这个来判断纠缠质量。
3.6 本章知识体系
下面这张图总结了Qobj的核心概念和操作流程,你可以把它当作一个快速参考。
这张图把Qobj的创建、属性和运算串在了一起。你从创建开始,然后检查属性确认类型,最后进行各种运算。这个流程我每次写仿真代码都会走一遍,已经成了肌肉记忆。
3.7 小结
Qobj是QuTiP的基石,理解它就等于拿到了量子光学仿真的钥匙。记住三点:创建时指定维度、用内置函数减少错误、运算前检查类型。做到这三点,你的仿真代码至少能少一半的bug。
嗯,这一章的内容就到这里。代码不多,但都是实打实的干货。你可以在自己的环境里把示例跑一遍,感受一下Qobj的“智能”之处。