简介:面向量子信息方向的MATLAB实用工具箱,整合了量子比特操作、量子门实现、量子线路模拟、量子测量以及熵与纠缠度量等功能,适合科研人员、工程师与相关专业学生开展量子算法仿真和量子系统性质分析。压缩包共279个文件,以276个.m脚本为主,辅以3个txt说明文档,整体仅163KB,轻量易用。核心脚本与初始化配置分别提供库函数调用与环境预设,启动加载模块确保每次使用自动生效;dm、measurements、entropy_and_entanglement等目录则按密度矩阵计算、量子测量和纠缠度量分模块组织,查阅路径清晰。配套README.txt、release_notes.txt和LICENSE.txt可帮助快速上手、获知更新与许可条款。资源已有545人学习,想深入量子信息计算模拟的读者可由此获得一套结构完整、可直接调用的MATLAB基础工具。
1. 量子信息计算库:把态矢量演化和测量封装成可用的 MATLAB 工具箱
做量子计算仿真的人大多经历过这种尴尬:算法流程画在纸上很清晰,一落到 MATLAB 里就开始和复数矩阵搏斗。态矢量乘法、张量积顺序、测量坍缩概率,每一步都是出错重灾区,调试一个小规模量子线路常常要花掉一整个下午。这个量子信息计算库就是为解决这类问题而生的:它把量子态表示、量子门操作、测量与线路模拟封装成一组 MATLAB 函数和类,让开发者从底层矩阵运算中解脱出来。无论是刚接触量子计算的初学者,还是需要快速验证算法的研究者,都能靠这份源码把思路直接变成可运行的仿真结果,改参数、换门、加测量,几乎不用动框架代码。
2. 总体架构:围绕 QuantumState、QuantumGate、QuantumCircuit 的三层拆解
先说结论:这个库的架构可以概括为“两个核心类、一个调度类、三个辅助模块”。QuantumState负责量子态的存储与基础运算,QuantumGate负责门的定义与组合,QuantumCircuit负责把门按时间序排成线路并执行仿真。上层再挂测量模块和可视化工具,整套分层方式和主流量子计算框架的思路基本一致,后面想迁移到别的语言也容易对照。
2.1 三层职责划分:为什么把态和门拆成两个类而不是塞进一个对象
在 MATLAB 里做量子仿真的常见做法,是直接拿复数矩阵塞进脚本,然后靠一堆过程式函数操作这些矩阵。这样做在 2 到 3 个量子比特时还好,一旦超过 4 个比特,就会陷入“哪个矩阵乘哪个矩阵”的黑匣子状态。这个库选择了面向对象的分层:QuantumState只关心“我是谁”,也就是当前系统的态矢量或密度矩阵;QuantumGate只关心“我要做什么变换”,也就是一个酉矩阵;QuantumCircuit把两者组合起来,按时间序调度门的执行。
这样拆的好处体现在两个地方。第一,门本身是可复用的,同一个 H 门可以在不同线路里反复使用,不用每次重新构造矩阵。第二,态的存储方式可以独立演进,比如从态矢量改成密度矩阵表示时,门和线路的代码不用大改。我自己写仿真代码时也倾向于这种职责划分,否则后期加噪声模型时会发现所有函数都要跟着改,那种牵一发动全身的感觉,经历过一次就不想再来第二次。
2.2 目录结构:拿到源码后第一眼看哪里
解压源码包后,第一件事应该是看目录结构。这个库的布局大致如下:
quantum-lib/ ├── @QuantumState % 量子态类:态矢量、密度矩阵、部分求迹 ├── @QuantumGate % 量子门类:单比特门、受控门、自定义门 ├── @QuantumCircuit % 线路类:门的追加、执行、深度统计 ├── @Measurement % 测量类:投影测量、概率采样 ├── utils/ % 辅助函数:张量积、矩阵归一化、随机数封装 ├── examples/ % 示例脚本:贝尔态、GHZ 态、量子隐形传态 └── docs/ % 文档和 API 说明@开头是 MATLAB 的类文件夹语法,表示该文件夹下的所有文件都属于这个类的方法。使用类文件夹的好处是不用手动addpath到每个子目录,MATLAB 会自动把类定义加载进搜索路径。初次使用时,只需要在仓库根目录执行一次addpath(genpath('quantum-lib')),后续所有类都能被直接调用,不用每次启动都重新配置一遍。
2.3 数据流:一次仿真从输入参数到输出结果经历了什么
把一次典型仿真的数据流理顺,能帮你在改代码时快速定位到具体模块。以制备并测量一个两比特纠缠态为例,整个流程是:先创建两个初始化为零态的QuantumState对象,接着用QuantumGate构造 H 门和 CNOT 门,然后通过QuantumCircuit按顺序把 H 门作用到比特 1、把 CNOT 门作用到比特 1 和比特 2,执行线路得到最终态,最后调用测量模块对最终态做投影测量,得到坍缩后的态和对应概率。
这个流程对应到代码里就是三层对象各司其职,出问题时可以按层排查:概率算得不对,先看测量模块;演化结果不符合预期,先看QuantumGate的矩阵构造;如果连初始态都错了,那就是QuantumState的问题。分层的价值在调试时体现得最明显,不用每次都在上千行脚本里翻来翻去。
3. 核心实现拆解:态矢量、酉变换与测量坍缩的代码链路
这一章挑三个最关键的模块,把核心代码拉出来逐段讲。代码做了适度简化,保留了原库的核心逻辑,方便阅读也方便直接抄走。
3.1 QuantumState:态矢量与密度矩阵的统一封装
量子态在仿真里有两个表示层级:纯态用态矢量,混态用密度矩阵。这个库在设计QuantumState时做了一个实用的决定:内部data属性统一存复数矩阵,纯态存列向量,混态存方阵,通过is_pure标志区分。
classdef QuantumState < handle properties data; % 复数矩阵,纯态为列向量,混态为方阵 nq; % 量子比特数 is_pure = true; end methods function obj = QuantumState(init_state) % 支持传入态矢量或密度矩阵 if isvector(init_state) obj.data = init_state(:); obj.nq = log2(length(init_state)); obj.is_pure = true; % 归一化态矢量 n = norm(obj.data); if abs(n - 1) > 1e-12 obj.data = obj.data / n; end else obj.data = init_state; obj.nq = log2(size(init_state, 1)); obj.is_pure = false; % 密度矩阵归一化:迹等于 1 tr = trace(obj.data); if abs(tr - 1) > 1e-12 obj.data = obj.data / tr; end end end function rho = density_matrix(obj) % 纯态转密度矩阵,混态直接返回 if obj.is_pure rho = obj.data * obj.data'; else rho = obj.data; end end end end构造函数接收一个复数向量或方阵,通过取对数自动推断比特数nq,并在创建时就完成归一化。这里的关键细节是归一化阈值1e-12:量子线路跑完一轮之后,由于浮点误差,振幅模方和往往不是精确的 1,直接做后续运算会积累偏差,所以在入口统一处理掉。density_matrix方法是纯态和混态之间的桥梁,在计算期望值或绘制布洛赫球时非常有用。
注意这里继承的是handle类而不是值类,这是源码里一个值得留意的设计选择。handle是引用语义,两个变量可以指向同一个数据对象,在构造线路时可以避免反复复制大矩阵。但这也带来一个副作用:你改了其中一个变量,另一个也会跟着变。后面避坑章节会专门讲这个问题。
3.2 QuantumGate:从单比特门到受控门的矩阵组装
量子门的本质是酉矩阵。这个库的QuantumGate类把常用门(Pauli 门、H 门、S 门、T 门、CNOT、CZ、SWAP)预置为静态方法,也允许传入任意酉矩阵构造自定义门。
classdef QuantumGate properties matrix; % 酉矩阵 nq; % 门作用的比特数 name; % 门名称 end methods function obj = QuantumGate(matrix, name) % 校验酉性:M'*M 应接近单位阵 assert(norm(matrix' * matrix - eye(size(matrix))) < 1e-10, ... '输入矩阵必须是酉矩阵'); obj.matrix = matrix; obj.nq = log2(size(matrix, 1)); obj.name = name; end end methods (Static) function g = H() g = QuantumGate(1/sqrt(2) * [1 1; 1 -1], 'H'); end function g = CNOT(nq, ctrl, tgt) % nq : 系统总比特数 % ctrl: 控制比特编号(1 到 nq) % tgt : 目标比特编号(1 到 nq,且不等于 ctrl) dim = 2^nq; m = eye(dim); for idx = 0:dim-1 bits = dec2bin(idx, nq) - '0'; % 转成二进制位向量 if bits(ctrl) == 1 bits(tgt) = 1 - bits(tgt); % 翻转目标比特 new_idx = bin2dec(num2str(bits)) + 1; m(idx + 1, :) = 0; m(idx + 1, new_idx) = 1; end end g = QuantumGate(m, sprintf('CNOT(%d,%d)', ctrl, tgt)); end end end构造方法里的酉性校验用的是M' * M,也就是共轭转置,不是普通转置transpose。这个细节对复矩阵极其关键,很多人自定义门时会把'写成.',结果矩阵平方和算出来是复数,断言直接不通过。CNOT 构造的逻辑是对每个计算基编号逐位判断:把编号转成二进制位串后,检查控制位是否为 1,是的话就把目标位翻转,再把新的编号映射到单位阵对应行上。这个方法的时间复杂度是O(4^n),能覆盖到 10 比特以内的大部分教学和验证场景。
3.3 测量模块:概率计算、随机采样与坍缩实现
测量是量子线路里最容易写错的部分,关键在两点:概率要按振幅模平方计算,坍缩要按测量结果筛选分量。这个库的测量函数把整条逻辑封装在一个函数里。
function [outcome, probs, collapsed] = measure_state(state, qubits_to_measure) % state : QuantumState 对象 % qubits_to_measure : 要测量的比特编号向量,例如 [1 2] % outcome : 测量结果位串,如 [0 1] % probs : 每种结果出现的概率 % collapsed : 坍缩后的量子态(未归一化,需要再归一化) if state.is_pure nq = state.nq; psi = state.data; n_meas = numel(qubits_to_measure); dim_meas = 2^n_meas; probs = zeros(dim_meas, 1); % 第一遍:对所有计算基分量按测量比特分组累加概率 for i = 1:numel(psi) bits = dec2bin(i - 1, nq) - '0'; meas_bits = bits(qubits_to_measure); group_idx = bin2dec(num2str(meas_bits)) + 1; probs(group_idx) = probs(group_idx) + abs(psi(i))^2; end % 归一化概率 probs = probs / sum(probs); % 第二遍:按概率随机采样,确定测量结果 r = rand(); cumprobs = cumsum(probs); chosen = find(cumprobs >= r, 1, 'first'); chosen_bits = dec2bin(chosen - 1, n_meas) - '0'; % 第三遍:坍缩,只保留与测量结果一致的分量 collapsed = zeros(size(psi)); for i = 1:numel(psi) bits = dec2bin(i - 1, nq) - '0'; if isequal(bits(qubits_to_measure), chosen_bits') collapsed(i) = psi(i); end end outcome = chosen_bits; collapsed = collapsed / norm(collapsed); % 重新归一化 else error('measure_state 暂不支持混态的测量,请先转为纯态'); end end这个函数做了三件事:先遍历所有计算基振幅,按测量比特的不同取值分组累加概率,相当于做了一次“部分求迹”;然后用rand()做加权随机采样,模拟测量坍缩的随机性;最后把所有不满足测量结果的分量清零,再归一化,得到坍缩后的态。三步对应着量子力学测量公理的三个要求,少一步结果都是错的。rand()每次返回 0 到 1 之间的均匀随机数,find(cumprobs >= r, 1, 'first')实现的是概率加权抽样,哪个区间被命中,就得到哪个测量结果。
4. 从零跑通:路径配置、贝尔态制备和概率验证三步走
这一章把环境配置和运行流程完整过一遍,照着做就能跑出结果。我建议在 MATLAB R2020b 及以上版本运行,低版本对 classdef 的支持虽然也能跑,但性能会差一些。
4.1 环境准备与路径加载
打开 MATLAB 后,先 cd 到仓库根目录,然后执行一次路径加载:
% 当前目录指向 quantum-lib 所在文件夹 addpath(genpath(pwd)); savepath; % 保存路径,下次启动不用重新配置genpath(pwd)会递归地把当前目录下所有子文件夹加入搜索路径,包括那些@开头的类文件夹。savepath把路径配置写入 MATLAB 的 pathdef 文件,这样下次重启 MATLAB 不用重复操作。如果你用的是 MATLAB Online 或者校园机房的共享环境,可能没有写 pathdef 的权限,那每次会话开始手动执行一次addpath(genpath(pwd))即可。
4.2 跑通一个贝尔态制备线路
贝尔态是量子信息里最基础的纠缠态,制备线路只有两步:H 门作用到第一个比特,再 CNOT 把第一个比特作为控制位、第二个比特作为目标位。
% 创建两比特系统的初始态 |00> q1 = QuantumState([1; 0]); q2 = QuantumState([1; 0]); psi0 = kron(q1.data, q2.data); system = QuantumState(psi0); % 构建线路 circ = QuantumCircuit(system); circ.add_gate(QuantumGate.H(), 1); % H 门作用到比特 1 circ.add_gate(QuantumGate.CNOT(2, 1, 2), [1, 2]); % CNOT: 控制 1 目标 2 % 执行线路 final_state = circ.run(); % 测量两个比特 [outcome, probs, collapsed] = measure_state(final_state, [1, 2]); disp(probs); % 期待看到 [0.5; 0.5; 0; 0] 之类的结果这里kron函数按从左到右的顺序做张量积,对应比特 1 在前、比特 2 在后,索引顺序一旦搞混,后续所有结果都会乱掉。circ.add_gate的第一个参数是门对象,第二个参数是门作用的比特位置,位置编号从 1 开始。执行完线路后,measure_state返回的probs理论上应该只有[0 0]和[1 1]两种结果的概率,并且各占一半。
4.3 验证结果正确性:不只看概率,还要看态矢量
只跑出概率还不够,建议把最终态矢量也打印出来,用数学期望验证一下。贝尔态的标准形式是(|00⟩ + |11⟩) / sqrt(2),对应到态矢量就是四个分量为[1/sqrt(2); 0; 0; 1/sqrt(2)]。你可以在线路执行后检查:
% 查看最终态矢量的非零分量 nonzero_idx = find(abs(final_state.data) > 1e-8); disp(nonzero_idx); disp(final_state.data(nonzero_idx));如果打印出来是第 1 个和第 4 个分量为0.7071,说明线路逻辑正确。如果你发现翻转了过来,比如是[0; 0.7071; 0.7071; 0],不用怀疑别的,就是你kron的顺序或者 CNOT 的控制目标写反了。这是我最常遇到的翻车现场,所以每一步都建议验一下。
提示:概率结果有随机性,单次测量看到
[0 0]或[1 1]都很正常。想验证概率分布,用一个循环跑 1000 次测量取频率,或者用rng(42)固定随机种子再重跑。
5. 避坑记录:张量积顺序、测量概率与内存爆炸的五个坑
这个章节每条都是实际跑仿真时容易踩进去的坑,按“现象 → 原因 → 解决”的格式记录,方便你快速对照排查。
5.1 张量积顺序搞反:所有概率对不上号
现象:跑贝尔态制备,测量结果的概率分布完全对不上理论值,[0 1]和[1 0]出现了不该有的概率。原因:MATLAB 的kron是严格按输入顺序做张量积的,而量子线路的比特编号习惯从左到右排列,很多人把kron(q1.data, q2.data)写成了kron(q2.data, q1.data),整个矢量空间里的坐标顺序全部镜像翻转。解决:在创建初始态时固定写成从左到右的编号顺序,并且加一行注释标明“比特 1 在前”。如果你觉得记号太绕,可以封装一个工具函数combine_states(varargin),内部用kron逐个相乘,并做一次维度校验,函数返回的nq必须等于输入参数个数,否则直接抛错。
5.2 测量概率与随机数的玄学:重复采样结果总是偏向一边
现象:同样的线路跑很多次,测量结果里[0 0]出现的次数明显多于[1 1],反复重试都一样。原因:大概率是概率计算时取了psi(i)的平方而不是模平方。复数振幅a + bi的平方是(a+bi)^2,模平方才是a^2+b^2。MATLAB 里abs(psi(i))^2和real(psi(i))^2是两回事。解决:检查所有概率累加的地方,统一用abs(...)^2;另外如果你的脚本在循环里调用了rand而没设随机种子,重复试验之间会有偏差感,建议用rng(2024)这类固定种子复现,排除玄学因素。
5.3 超过 20 个比特后内存开始吃紧
现象:从 20 个比特开始,每次run()执行时间越来越长,到 24 个比特时直接卡出内存不足的错误。原因:态矢量存储空间随比特数指数增长,2^24个复数的双精度矩阵需要约 256MB 内存,而这只是一份数据;如果代码里用了值传递或者不小心复制了对象,内存占用直接翻倍。解决:一是尽量用handle类避免对象复制;二是对于只需要部分子系统的线路,考虑用密度矩阵做部分求迹,不要保留完整态空间;三是可以尝试用gpuArray把矩阵乘法挪到 GPU 上,遇到 20 个比特以上的大矩阵运算,GPU 带来的速度提升不是一点半点。
5.4 自定义门报“输入矩阵必须是酉矩阵”
现象:自己构造了一个相位门[1 0; 0 exp(1i*theta)],传入QuantumGate构造函数时直接断言失败。原因:浮点误差累计导致M'*M和单位阵的差超过了 1e-10 的容差;另一类可能是把共轭转置写成了普通转置,复矩阵的虚部没被正确处理,矩阵乘出来不是单位阵。解决:构造自定义门前先手动验证norm(M'*M - eye(size(M))),如果是因为浮点误差,把构造函数里的容差放宽到1e-8;如果是因为转置写错,把.'改成'。从这之后我每次写新门都会先跑一遍酉性检查,成了固定流程。
5.5 handle 引用语义导致变量互相污染
现象:创建了两个QuantumState对象,只是想改其中一个,结果发现另一个的数据也变了。原因:QuantumState < handle是引用语义,多个变量持有同一个底层数据地址,任何一处修改都会同步。这在构造线路时省了复制开销,但对不熟悉 MATLAB 面向对象的人来说非常反直觉。解决:如果你确实需要独立副本,自己实现一个copy方法,把data和标志位复制到新对象里返回;MATLAB内置的copy函数对 handle 类默认是浅拷贝,使用时要注意区分。知道这个坑之后,我再写多比特仿真时都会刻意避免把一个对象反复赋值给多个变量。
6. 二次开发思路:自定义门、噪声信道与性能优化的改造点
这个库的扩展方式比想象中要直接,核心思路是“不修改现有类,只新增子类或新增静态方法”。比如要加一个自定义单比特门,只需要构造QuantumGate实例,不必改动类定义本身:
theta = pi/3; phase_gate = QuantumGate([1 0; 0 exp(1i*theta)], 'P(pi/3)'); circ.add_gate(phase_gate, 1);加噪声模型稍微绕一点,但也可以独立于框架实现。常见做法是在线路run()之后,对最终密度矩阵做一次信道变换,比如退极化信道:
function noisy_rho = depolarizing_channel(rho, p) % rho: 输入密度矩阵 % p : 退极化概率,0 到 1 之间 d = size(rho, 1); noisy_rho = (1 - p) * rho + p * eye(d) / d; end调用时先用density_matrix()把纯态转成密度矩阵,再过一次信道。性能优化方面,我一般从三个方向入手:一是把QuantumCircuit.run()里循环调用的矩阵乘法改成预先合并所有门矩阵的形式,减少中间矩阵数量;二是对只涉及少数比特的门,用稀疏矩阵存储,避免单位阵撑满内存;三是在 MATLAB 支持 GPU 的环境中,把QuantumState.data转成gpuArray,大矩阵乘法直接交给 GPU 处理。每次改完记得跑一遍贝尔态和 GHZ 态的验证脚本,确认新模块没有破坏原有的酉性约束和概率归一化。从那以后我每次加完功能都强制走一遍三个验证点:矩阵酉性、概率归一化、两比特纠缠态保真度,成了肌肉记忆。这个习惯帮我拦住了不少看似没问题、实际改了索引顺序的隐形 bug,希望也能帮到你。
本文还有配套的精品资源,点击获取