跳转到内容

Lesson 70: 量子比特门电路

练习任务

难度:中

用 C 语言实现一个单量子比特模拟器,模拟量子态在 Hadamard 门(H)、Pauli-X 门(X)和 Pauli-Z 门(Z)作用下的演化,并最终执行 100 次量子测量。需要完成 6 个核心函数:

  1. complex_mult() — 复数乘法:(a+bi)(c+di) = (ac−bd) + (ad+bc)i
  2. complex_add() — 复数加法:(a+bi)+(c+di) = (a+c)+(b+d)i
  3. apply_gate() — 将 2×2 量子门矩阵作用于量子态向量
  4. print_state() — 以 Dirac 符符号格式输出量子态
  5. measure() — 模拟一次投影测量,基于 LCG 伪随机数
  6. main() — 驱动模拟流程:初始化 |0⟩ → H → X → Z → H → 100 次测量

门矩阵常量(HXZ)和 Complex 结构体已由框架提供。

cd exercises/70_qubit-gate-simulator
make test

验证:编译运行后与 expected_output.txt 逐行 diff 比较,完全匹配则通过。

提示apply_gate 中必须使用临时变量保存 new0new1,计算完两个分量后再写回。若先写回 state[0] 再计算 state[1],读取到的 state[0] 已是新值而非旧值。complex_mult 的虚部是 ad+bc,不是减号—这是最常见的符号错误。


核心知识点

  • 量子比特的数学表示|ψ⟩ = α|0⟩ + β|1⟩,α、β 是复数振幅,满足归一化条件 |α|² + |β|² = 1;Bloch 球将任意纯态映射到单位球面
  • 复数结构体与运算Complex {double real, imag},复乘公式 (ac−bd) + (ad+bc)i,复加直接分量相加
  • 量子门作为酉矩阵 — 量子门是 2×2 酉矩阵(U†U = I),保证可逆且保持归一化;Hadamard 门制造叠加态、Pauli-X 翻转比特、Pauli-Z 翻转相位
  • apply_gate 的实现细节 — 矩阵-向量乘法 G|ψ⟩,必须用临时变量避免"写后读"数据竞争;C 语言中 2D 数组函数参数传递
  • 量子测量与 Born 规则 — 以概率 |α|² 坍缩到 |0⟩、以概率 |β|² 坍缩到 |1⟩;LCG 伪随机模拟可复现的测量结果
  • 电路演化追踪 — H→X→Z→H 的完整状态追踪:|0⟩→|+⟩→|+⟩→|−⟩→|1⟩,理解每一门的 Bloch 球旋转
  • 酉矩阵恒等式 — H²=I、X²=I、Z²=I、HXH=Z、HZH=X,Hadamard 门作为 Pauli 矩阵基底转换器

代码框架

70_qubit_gate_simulator.c
c
#include <math.h>
#include <stdio.h>

/* Complex number type (provided) */
typedef struct {
    double real;
    double imag;
} Complex;

/* 1/√2 for Hadamard gate */
#define SQRT2_2 0.7071067811865476

/* Gate matrices — provided as constants */
static const Complex H[2][2] = {
    {{SQRT2_2, 0}, {SQRT2_2, 0}},
    {{SQRT2_2, 0}, {-SQRT2_2, 0}},
};

static const Complex X[2][2] = {
    {{0, 0}, {1, 0}},
    {{1, 0}, {0, 0}},
};

static const Complex Z[2][2] = {
    {{1, 0}, {0, 0}},
    {{0, 0}, {-1, 0}},
};

/* ─── TODO 1: complex_mult ───
 * (a+bi)(c+di) = (ac-bd) + (ad+bc)i */
static Complex complex_mult(Complex a, Complex b) {
    // ① result.real = a.real*b.real - a.imag*b.imag
    // ② result.imag = a.real*b.imag + a.imag*b.real
    // ③ return result
}

/* ─── TODO 2: complex_add ───
 * (a+bi)+(c+di) = (a+c)+(b+d)i */
static Complex complex_add(Complex a, Complex b) {
    // ① result.real = a.real + b.real
    // ② result.imag = a.imag + b.imag
    // ③ return result
}

/* ─── TODO 3: apply_gate ───
 * |ψ'⟩ = G|ψ⟩: new_state = G × state
 * 关键:必须用临时变量! */
static void apply_gate(const Complex gate[2][2], Complex state[2]) {
    // ① 用 complex_mult + complex_add 计算:
    //    new0 = gate[0][0]*state[0] + gate[0][1]*state[1]
    // ② 计算 new1 = gate[1][0]*state[0] + gate[1][1]*state[1]
    // ③ 将 new0、new1 写回 state[0]、state[1]
}

/* ─── TODO 4: print_state ───
 * 格式必须精确匹配 expected_output.txt */
static void print_state(const char *label, const Complex state[2]) {
    // ① printf("%s\n", label)
    // ② printf("( %+f %+fi )|0⟩ + ( %+f %+fi )|1⟩\n",
    //    state[0].real, state[0].imag,
    //    state[1].real, state[1].imag)
    // ③ printf("\n")
}

/* ─── TODO 5: measure ───
 * Born rule: P(|0⟩) = |α|², P(|1⟩) = |β|²
 * LCG: seed = (1103515245u * seed + 12345u) & 0x7FFFFFFFu */
static int measure(const Complex state[2], unsigned int *seed) {
    // ① double prob0 = state[0].real*state[0].real
    //                 + state[0].imag*state[0].imag
    // ② *seed = (1103515245u * (*seed) + 12345u) & 0x7FFFFFFFu
    // ③ double r = (double)(*seed) / (double)0x80000000u
    // ④ if (r < prob0) return 0; else return 1
}

/* ─── TODO 6: main ───
 * 流程: |0⟩ → H → X → Z → H → 测量 100 次 */
int main(void) {
    // ① 初始化: state[0] = {1.0, 0.0}, state[1] = {0.0, 0.0}
    // ② print_state("Initial state |0⟩:", state)
    // ③ apply_gate(H, state) → print_state
    // ④ apply_gate(X, state) → print_state
    // ⑤ apply_gate(Z, state) → print_state
    // ⑥ apply_gate(H, state) → print_state
    // ⑦ int counts[2] = {0,0}; unsigned int seed = 42u
    // ⑧ for 100 次: { o = measure(state, &seed); counts[o]++; }
    // ⑨ printf 测量统计: |0⟩: %d, |1⟩: %d (含百分比)
    // ⑩ return 0
}

阅读骨架后,尝试自己填充 // ①// ⑩ 标记的部分。核心挑战在于:complex_mult 的虚部符号是加还是减?apply_gate 为什么必须用临时变量?measure0x80000000u 为什么不能写成 0x80000000?最终 H→X→Z→H 序列的净效果是什么?

TIP

先不要往下翻看参考解答。拿纸笔手动计算 H·Z·X·H|0⟩ 的矩阵-向量乘积每一步——|0⟩ → |+⟩ → |+⟩ → |−⟩ → |1⟩。留意每一步 α 和 β 的实部、虚部变化,以及测量概率的演变。


深度讲解

1. 量子比特的数学表示——从经典比特到叠加态

1.1 经典比特 vs 量子比特

经典比特:                     量子比特:
  ┌───┐                         ┌─────────────────┐
 0 1  α|0⟩ + β|1⟩
  └───┘       └───┘  |α|² + |β|² = 1
                                └─────────────────┘
  只能处于两个确定状态之一       可以处于叠加态 (Superposition)

量子比特(qubit)是量子计算的基本信息单元。与经典比特只能取 0 或 1 不同,量子比特可以同时处于 |0⟩ 和 |1⟩ 的线性叠加态:

qubit_state.c
c
/* 单量子比特状态:二维复向量 */
Complex state[2];

/* |0⟩ 基态:state[0] = 1, state[1] = 0 */
state[0] = (Complex){1.0, 0.0};
state[1] = (Complex){0.0, 0.0};

/* |1⟩ 基态:state[0] = 0, state[1] = 1 */
state[0] = (Complex){0.0, 0.0};
state[1] = (Complex){1.0, 0.0};

/* |+⟩ 叠加态 (均匀叠加):(|0⟩+|1⟩)/√2 */
state[0] = (Complex){0.707107, 0.0};
state[1] = (Complex){0.707107, 0.0};
状态向量的完整表示:

  |ψ⟩ = α|0⟩ + β|1⟩ =  α
  β

  α = state[0].real + i·state[0].imag
  β = state[1].real + i·state[1].imag

  归一化条件: |α|² + |β|² = α.real² + α.imag² + β.real² + β.imag² = 1

1.2 Dirac 符号(Bra-Ket Notation)

符 符号含义向量表示
`0⟩`计算基态 0
`1⟩`计算基态 1
`ψ⟩`任意量子态
`⟨ψ`对偶向量
`⟨φψ⟩`内积

NOTE

"ket" |ψ⟩ 是列向量,"bra" ⟨ψ| 是行向量(共轭转置)。这个记法源自物理学家 Dirac,命名取自 bracket(括号)的 split——bra + c + ket = bracket。

1.3 Bloch 球——单量子比特的几何图像

任何单量子比特的纯态都可以映射到单位球面上的一点:

                    |0⟩ (北极)

                   /│\
                  / \
                 /  \
                /   \  任意状态 |ψ⟩
               /    \
              /     \
             /      \
            └───────┼───────┘


                   |1⟩ (南极)

  |ψ⟩ = cos(θ/2)|0⟩ + e^() sin(θ/2)|1⟩

  θ [0, π]    极角 决定 |0⟩ |1⟩ 的概率分布
  φ [0, )   方位角 — 决定相对相位
状 状态Bloch 球位置θφαβ
`0⟩`北极01
`1⟩`南极π0
`+⟩`X 轴正方向π/201/√2
`−⟩`X 轴负方向π/2π1/√2
`+i⟩`Y 轴正方向π/2π/21/√2

TIP

Bloch 球可视化单量子比特的最强力工具。量子门在 Bloch 球上对应旋转——球面上的点绕某轴旋转一定角度。六种重要旋转轴:X、Y、Z、及其组合。


2. 复数运算的 C 语言实现——complex_multcomplex_add

2.1 Complex 结构体设计

complex_type.c
c
/* 复数 a+bi:用两个 double 字段表示 */
typedef struct {
    double real;  /* 实部 a */
    double imag;  /* 虚部 b */
} Complex;

/* 示例值:
 *   (Complex){1.0, 2.0}     → 1 + 2i
 *   (Complex){0.0, 1.0}     → i
 *   (Complex){SQRT2_2, 0.0} → 1/√2 + 0i
 */

WARNING

C99/C11 标准库 <complex.h> 提供了原生复数类型 double complex。但本题手动实现 Complex 结构体是为了深入理解复数运算的底层——知道 (a+bi)(c+di) 展开后实部和虚部各自是什么。

2.2 复数乘法——(ac−bd) + (ad+bc)i

complex_mult_demo.c
c
/* 复数乘法公式推导:
 *
 *   (a + bi)(c + di)
 *   = a·c + a·di + bi·c + bi·di
 *   = ac + adi + bci + bd·i²
 *   = ac + adi + bci - bd          ← i² = -1
 *   = (ac - bd) + (ad + bc)i
 */

Complex complex_mult(Complex a, Complex b) {
    Complex result;
    result.real = a.real * b.real - a.imag * b.imag;  /* ac - bd */
    result.imag = a.real * b.imag + a.imag * b.real;  /* ad + bc */
    return result;
}

/* 验证: (1+2i) * (3+4i) = (3-8) + (4+6)i = -5 + 10i */
// complex_mult((Complex){1,2}, (Complex){3,4}) → real=-5, imag=10 ✓

CAUTION

最常见的符号错误:把 result.imag = a.real*b.imag + a.imag*b.real 写成减号 a.real*b.imag - a.imag*b.real。复乘的虚部是 ad + bc,不是减号!这个错误会导致最终状态演化全错。

2.3 复数加法——直接分量相加

complex_add_demo.c
c
/* 复数加法: (a+bi) + (c+di) = (a+c) + (b+d)i */

Complex complex_add(Complex a, Complex b) {
    Complex result;
    result.real = a.real + b.real;
    result.imag = a.imag + b.imag;
    return result;
}

/* 验证: (1+2i) + (3+4i) = 4 + 6i */
// complex_add((Complex){1,2}, (Complex){3,4}) → real=4, imag=6 ✓

3. 量子门——酉矩阵的物理意义

3.1 量子门 = 2×2 酉矩阵

量子门用 2×2 酉矩阵(unitary matrix)表示。酉矩阵满足 U†U = I(共轭转置等于逆),这保证:

  • 量子操作可逆(有逆矩阵)
  • 量子操作保持归一化(|α|²+|β|²=1 在执行门后仍成立)
门作用: |ψ'⟩ = G |ψ⟩

       ┌ g₀₀  g₀₁ ┐     ┌ α ┐   ┌ g₀₀·α + g₀₁·β ┐
|ψ' = g₁₀  g₁₁  × β = g₁₀·α + g₁₁·β

3.2 本题使用的三个量子门

Hadamard 门(H)— 制造叠加态

hadamard_gate.c
c
/*
 * H = 1/√2 [[1,  1],
 *           [1, -1]]
 *
 * 作用:
 *   H|0⟩ = (|0⟩+|1⟩)/√2 = |+⟩    ← 确定态 → 叠加态
 *   H|1⟩ = (|0⟩−|1⟩)/√2 = |−⟩
 *
 * 性质:
 *   H² = I (自逆——施加两次回到原态)
 *   H|+⟩ = |0⟩, H|−⟩ = |1⟩       ← 叠加态 → 确定态
 *   Bloch 球: 绕 (X+Z)/√2 轴旋转 π
 *
 * H 是 QFT (量子傅里叶变换) 的单比特特例
 */
static const Complex H[2][2] = {
    {{0.707107, 0}, { 0.707107, 0}},
    {{0.707107, 0}, {-0.707107, 0}},
};

Pauli-X 门(X)— 量子 NOT 门

pauli_x_gate.c
c
/*
 * X = [[0, 1],
 *      [1, 0]]
 *
 * 作用:
 *   X|0⟩ = |1⟩
 *   X|1⟩ = |0⟩
 *
 * 性质:
 *   X² = I (施加两次回到原态)
 *   Bloch 球: 绕 X 轴旋转 π (180°)
 *
 * X 是量子计算中的 NOT 门——但,它可以作用于叠加态!
 */
static const Complex X[2][2] = {
    {{0, 0}, {1, 0}},
    {{1, 0}, {0, 0}},
};

Pauli-Z 门(Z)— 相位翻转

pauli_z_gate.c
c
/*
 * Z = [[1,  0],
 *      [0, -1]]
 *
 * 作用:
 *   Z|0⟩ =  |0⟩         ← |0⟩ 不受影响
 *   Z|1⟩ = -|1⟩         ← |1⟩ 相位翻转 (乘以 -1)
 *
 * 性质:
 *   Z² = I
 *   Bloch 球: 绕 Z 轴旋转 π (180°)
 *
 * 相位翻转不改变测量概率 (|−β|² = |β|²),但在干涉效应中至关重要
 */
static const Complex Z[2][2] = {
    {{1, 0}, { 0, 0}},
    {{0, 0}, {-1, 0}},
};

3.3 量子门的通用性质

酉矩阵的验证——以 H 为例:

  H = 1/√2 [[1,  1],    H† = 1/√2 [[1,  1],    (共轭转置)
           [1, -1]]               [1, -1]]

  H†·H = 1/2 [[1,  1],  [[1,  1],  = 1/2 [[1·1+1·1,  1·1+1·(−1)],
             [1, -1]]   [1, -1]]          [1·1+(−1)·1, 1·1+(−1)·(−1)]]

        = 1/2 [[2, 0],  = [[1, 0],  = I
               [0, 2]]     [0, 1]]

IMPORTANT

量子门的线性性:U(α|0⟩+β|1⟩) = αU|0⟩ + βU|1⟩。这意味着我们只需知道门对基态的作用,就能推导出对任意叠加态的作用。这是量子计算简洁描述的根本原因。


4. apply_gate——矩阵-向量乘法的正确实现

4.1 核心算法

|ψ'⟩ = G|ψ⟩

[α']   [G₀₀  G₀₁]   [α]   [G₀₀·α + G₀₁·β]
'] = [G₁₀  G₁₁] × [β] = [G₁₀·α + G₁₁·β]

每个乘法是 complex_mult,每个加法是 complex_add

4.2 临时变量陷阱——"写后读"数据竞争

apply_gate_correct.c
c
/* ✓ 正确实现——使用临时变量 */
static void apply_gate(const Complex gate[2][2], Complex state[2]) {
    Complex new0 = complex_add(
        complex_mult(gate[0][0], state[0]),
        complex_mult(gate[0][1], state[1])
    );
    Complex new1 = complex_add(
        complex_mult(gate[1][0], state[0]),
        complex_mult(gate[1][1], state[1])
    );
    state[0] = new0;
    state[1] = new1;
}
apply_gate_wrong.c
c
/* ✗ 错误实现——state[0] 被提前覆盖 */
static void apply_gate_wrong(const Complex gate[2][2], Complex state[2]) {
    /* 计算 new0 并写回 state[0] */
    state[0] = complex_add(
        complex_mult(gate[0][0], state[0]),  /* ← 读旧 state[0] */
        complex_mult(gate[0][1], state[1])
    );
    /* 此时 state[0] 已是新值! */
    /* 接下来计算 new1 时读到的 state[0] 是错的! */
    state[1] = complex_add(
        complex_mult(gate[1][0], state[0]),  /* ← 错误!读出的是新值 */
        complex_mult(gate[1][1], state[1])
    );
}
错误追踪——以 H 门作用于 |0⟩ = [1, 0] 为例:

  正确计算:
    new0 = 0.707·1 + 0.707·0 = 0.707
    new1 = 0.707·1 + (−0.707)·0 = 0.707
    state = [0.707, 0.707] (|+⟩)

  错误计算:
    state[0] = 0.707·1 + 0.707·0 = 0.707 写回 state[0]
    state[1] = 0.707·0.707 + (−0.707)·0 = 0.5  ← 读到的 state[0]=0.707 而不是 1!
    state = [0.707, 0.5] (错误状态!)

CAUTION

这是单线程程序中的数据竞争——不是多线程导致的,而是同一个表达式求值顺序导致的。计算 new1 依赖原始的 state[0],但 state[0] 已被覆盖。这个 bug 极其隐蔽——程序能编译、能运行,但结果悄悄错了。

4.3 2D 数组作为函数参数

const_gate_param.c
c
/* 门矩阵作为 const Complex gate[2][2] 传入
 *
 * C 中二维数组参数的写法:
 *   void f(const Complex gate[2][2], Complex state[2])
 *                            ^^^^^        ^^^^^
 *     gate[行][列]  → 行数 2 可省略     数组大小
 *
 * 访问方式: gate[row][column]
 *   gate[0][0]  → H[0][0] = {0.707107, 0}
 *   gate[0][1]  → H[0][1] = {0.707107, 0}
 *   gate[1][0]  → H[1][0] = {0.707107, 0}
 *   gate[1][1]  → H[1][1] = {-0.707107, 0}
 */

5. 量子测量——Born 规则与 LCG 随机模拟

5.1 Born 规则

量子测量是概率性的——这是量子力学与经典物理的根本区别:

测量 |ψ⟩ = α|0⟩ + β|1⟩:

  概率 P(|0⟩) = |α|² = α.real² + α.imag²
  概率 P(|1⟩) = |β|² = β.real² + β.imag²
        ─────────────────────────────────
         总和 = |α|² + |β|² = 1  (归一化保证)

  测量后: 状态坍缩 (collapse) 到测量结果对应的基态
  - |0⟩ 之后测量必得 |0⟩
  - |1⟩ 之后测量必得 |1⟩
  
  叠加信息永久丢失——测量是破坏性的、不可逆的!

5.2 LCG 伪随机数——可复现的随机性

本题使用线性同余生成器(Linear Congruential Generator, LCG)模拟测量中的随机性:

lcg_prng.c
c
/*
 * LCG 参数 (glibc rand() 经典参数):
 *   multiplier = 1103515245
 *   increment  = 12345
 *   modulus    = 2³¹ = 0x80000000u
 *
 * 递推公式:
 *   seed_{n+1} = (1103515245 × seed_n + 12345) mod 2³¹
 *
 * 归一化到 [0, 1):
 *   r = seed / 2³¹
 */

/* measure 函数的完整 LCG 使用 */
static int measure(const Complex state[2], unsigned int *seed) {
    double prob0 = state[0].real * state[0].real
                 + state[0].imag * state[0].imag;

    /* 推进 LCG */
    *seed = (1103515245u * (*seed) + 12345u) & 0x7FFFFFFFu;

    /* 归一化到 [0, 1) */
    double r = (double)(*seed) / (double)0x80000000u;

    /* Born 规则 */
    if (r < prob0) return 0;  /* 坍缩到 |0⟩ */
    else           return 1;  /* 坍缩到 |1⟩ */
}

CAUTION

0x80000000 作为 int 是 −2147483648(在有符号 32 位溢出),是未定义行为。必须加 u 后缀写 0x80000000u 确保是无符号类型。同理 0x7FFFFFFFu 确保高位截断正确。

5.3 为什么用 LCG 而不是 rand()

rand() 的实现在不同平台可能不同:
  - glibc (Linux): 一种 LCG
  - musl (Alpine): 另一种算法
  - MSVC (Windows): 又一种算法

 同一 seed 在不同平台产生不同序列
 expected_output.txt 无法精确预测

硬编码 LCG 的好处:
  - 参数固定 (1103515245, 12345, 2³¹)
  - seed=42u 产生完全相同序列
  - 所有平台一致 diff 测试可精确预测
  - 这也是为什么 seed 初始值必须是 42u

5.4 本题测量结果的确定性

本题最终状态 |ψ₄⟩ = |1⟩ = [0, 1]ᵀ

  所以:
    prob0 = + = 0.0
    r (0  r < 1) 永远 ≥ 0.0
 r < prob0 永远不成立
 100 次测量全部返回 |1⟩

这不是"运气好",而是确定性的——
    最终状态是纯 |1⟩,不是叠加态。

6. 电路演化完整追踪——H → X → Z → H

6.1 逐步演化

本题模拟的量子电路:

|0⟩ ──[H]──[X]──[Z]──[H]── 测量

初始状态 |ψ₀⟩ = |0⟩

|ψ₀⟩ =|0⟩ +|1⟩ = [1, 0]ᵀ
α = 1+0i, β = 0+0i
P(|0⟩) = 1² = 1.0 (100%),  P(|1⟩) = 0² = 0.0 (0%)
Bloch 球: |0⟩ = 北极,θ=0

Step 1 — H 门(产生叠加态)

|ψ₁⟩ = H|0⟩ = 1/√2 (|0⟩ + |1⟩) = |+⟩
      = [1/√2, 1/√2]ᵀ

α = 1/√2 0.707107,  β = 1/√2 0.707107
P(|0⟩) = P(|1⟩) = 0.5 ( 50%)
Bloch 球: X 轴正方向,θ=π/2, φ=0

Step 2 — X 门(交换振幅)

|ψ₂⟩ = X|ψ₁⟩ = X[1/√2, 1/√2]ᵀ = [1/√2, 1/√2]ᵀ = |+⟩

因为 α=β,交换后不变!
|+⟩ X 门的本征态(eigenstate),本征值为 +1.

Step 3 — Z 门(翻转相位)

|ψ₃⟩ = Z|ψ₂⟩ = Z[1/√2, 1/√2]ᵀ = [1/√2, −1/√2]ᵀ = |−⟩

α = 1/√2 0.707107,  β = −1/√2 −0.707107
P(|0⟩) = P(|1⟩) = 0.5 (概率不变,因为 |−β|² = |β|²)
Bloch 球: X 轴负方向,θ=π/2, φ=π

Step 4 — 第二个 H 门(回到计算基)

|ψ₄⟩ = H|ψ₃⟩ = H[1/√2, −1/√2]ᵀ = [0, 1]ᵀ = |1⟩

α = 0, β = 1
P(|0⟩) = 0%, P(|1⟩) = 100%
Bloch 球: |1⟩ = 南极,θ=π

6.2 演化总结表

| 步骤 | 操作 | 状态 | α | β | P(|0⟩) | P(|1⟩) | Bloch 位置 | | ---- | ---- | ---- | - | - | ------ | ------ | ---------- | | 0 | 初始 | |0⟩ | 1+0i | 0+0i | 100% | 0% | 北极 | | 1 | H | |+⟩ | 1/√2 | 1/√2 | 50% | 50% | X+ 轴 | | 2 | X | |+⟩ | 1/√2 | 1/√2 | 50% | 50% | X+ 轴(不变) | | 3 | Z | |−⟩ | 1/√2 | −1/√2 | 50% | 50% | X− 轴 | | 4 | H | |1⟩ | 0+0i | 1+0i | 0% | 100% | 南极 |

关键洞察H·Z·X·H|0⟩ = |1⟩,净效果等价于 X|0⟩。验证:

H·Z·X·H = H·Z·(X·H)
        = H·Z·(H·Z)        X·H = H·Z (验证: HXH=Z X=HZH XH=HZ)
        = (H·Z·H)·Z
        = X·Z HZH = X
        ...

实际上: H·Z·X·H = X 在忽略全局相位的意义下

NOTE

X|+⟩ = |+⟩ 是因为 |+⟩ 是 X 门的本征态——。这是量子力学中最优美的现象之一:特定态在操作下保持不变,操作只改变态的"大小"(乘以本征值),不改变"方向"。类似经典力学中的谐振子不动点。


参考解答

练习1&2: complex_mult 和 complex_add
solution_complex_ops.c
c
#include <math.h>
#include <stdio.h>

typedef struct {
    double real;
    double imag;
} Complex;

/* 复数乘法: (a+bi)(c+di) = (ac−bd) + (ad+bc)i */
static Complex complex_mult(Complex a, Complex b) {
    Complex result;
    result.real = a.real * b.real - a.imag * b.imag;
    result.imag = a.real * b.imag + a.imag * b.real;
    return result;
}

/* 复数加法: (a+bi)+(c+di) = (a+c)+(b+d)i */
static Complex complex_add(Complex a, Complex b) {
    Complex result;
    result.real = a.real + b.real;
    result.imag = a.imag + b.imag;
    return result;
}

/* 简单测试 */
int main(void) {
    Complex a = {1.0, 2.0};  /* 1+2i */
    Complex b = {3.0, 4.0};  /* 3+4i */

    Complex prod = complex_mult(a, b);
    printf("(%g+%gi)*(%g+%gi) = %g+%gi\n",
           a.real, a.imag, b.real, b.imag,
           prod.real, prod.imag);
    /* 预期: (1+2i)*(3+4i) = -5+10i */

    Complex sum = complex_add(a, b);
    printf("(%g+%gi)+(%g+%gi) = %g+%gi\n",
           a.real, a.imag, b.real, b.imag,
           sum.real, sum.imag);
    /* 预期: (1+2i)+(3+4i) = 4+6i */

    return 0;
}

要点:复乘的虚部是 ad+bc(加号!),不要写成减号。复加直接分量相加,无特殊注意点。

练习3: apply_gate — 矩阵-向量乘法
solution_apply_gate.c
c
#include <math.h>
#include <stdio.h>

typedef struct { double real; double imag; } Complex;

static Complex complex_mult(Complex a, Complex b) {
    Complex r;
    r.real = a.real * b.real - a.imag * b.imag;
    r.imag = a.real * b.imag + a.imag * b.real;
    return r;
}

static Complex complex_add(Complex a, Complex b) {
    Complex r;
    r.real = a.real + b.real;
    r.imag = a.imag + b.imag;
    return r;
}

/* 门矩阵常量 */
#define SQRT2_2 0.7071067811865476

static const Complex H[2][2] = {
    {{SQRT2_2, 0}, {SQRT2_2, 0}},
    {{SQRT2_2, 0}, {-SQRT2_2, 0}},
};

static const Complex X[2][2] = {
    {{0, 0}, {1, 0}},
    {{1, 0}, {0, 0}},
};

/* apply_gate: G · state → state (原地修改) */
static void apply_gate(const Complex gate[2][2], Complex state[2]) {
    Complex new0 = complex_add(
        complex_mult(gate[0][0], state[0]),
        complex_mult(gate[0][1], state[1])
    );
    Complex new1 = complex_add(
        complex_mult(gate[1][0], state[0]),
        complex_mult(gate[1][1], state[1])
    );
    state[0] = new0;
    state[1] = new1;
}

int main(void) {
    Complex state[2] = {{1.0, 0.0}, {0.0, 0.0}};  /* |0⟩ */

    /* H|0⟩ = (|0⟩+|1⟩)/√2 */
    apply_gate(H, state);
    printf("After H: α=(%g,%g), β=(%g,%g)\n",
           state[0].real, state[0].imag,
           state[1].real, state[1].imag);
    /* 预期: α≈(0.707,0), β≈(0.707,0) */

    /* X|+⟩ = |+⟩ */
    apply_gate(X, state);
    printf("After X: α=(%g,%g), β=(%g,%g)\n",
           state[0].real, state[0].imag,
           state[1].real, state[1].imag);
    /* 预期: α≈(0.707,0), β≈(0.707,0) — 不变 */

    return 0;
}

核心逻辑:用临时变量 new0new1 保存两个分量的计算结果,全部算完后再写回。每个乘法和加法都调用 complex_multcomplex_add。直接修改 state[0] 会导致 state[1] 的计算读到错误的值。

练习4&5: print_state 和 measure
solution_print_measure.c
c
#include <math.h>
#include <stdio.h>

typedef struct { double real; double imag; } Complex;

/* print_state — Dirac 符号输出,格式必须精确 */
static void print_state(const char *label, const Complex state[2]) {
    printf("%s\n", label);
    printf("( %+f %+fi )|0⟩ + ( %+f %+fi )|1⟩\n",
           state[0].real, state[0].imag,
           state[1].real, state[1].imag);
    printf("\n");
}

/* measure — LCG 伪随机 + Born 规则 */
static int measure(const Complex state[2], unsigned int *seed) {
    /* Born 规则: P(|0⟩) = |α|² */
    double prob0 = state[0].real * state[0].real
                 + state[0].imag * state[0].imag;

    /* LCG 推进: glibc rand() 参数 */
    *seed = (1103515245u * (*seed) + 12345u) & 0x7FFFFFFFu;

    /* 归一化到 [0, 1) */
    double r = (double)(*seed) / (double)0x80000000u;

    if (r < prob0) return 0;
    else           return 1;
}

int main(void) {
    /* |+⟩ = (|0⟩+|1⟩)/√2: prob0 = 0.5 */
    Complex state[2] = {{0.707107, 0.0}, {0.707107, 0.0}};
    unsigned int seed = 42u;
    int counts[2] = {0, 0};

    /* 1000 次测量 */
    for (int i = 0; i < 1000; i++) {
        int outcome = measure(state, &seed);
        counts[outcome]++;
    }

    printf("|0⟩: %d (%.1f%%)\n",
           counts[0], counts[0] * 100.0 / 1000);
    printf("|1⟩: %d (%.1f%%)\n",
           counts[1], counts[1] * 100.0 / 1000);
    /* 预期: 各约 50%,如 |0⟩: ~492, |1⟩: ~508 */

    return 0;
}

print_state 的关键:%+f 格式说明符自动显示正负号(+0.707107-0.707107)。measure 的关键:0x80000000u 必须是 unsigned int(带 u 后缀),否则有符号溢出是 UB。

练习6: main — 完整电路模拟
solution_main.c
c
#include <math.h>
#include <stdio.h>

typedef struct { double real; double imag; } Complex;

static Complex complex_mult(Complex a, Complex b) {
    Complex r;
    r.real = a.real * b.real - a.imag * b.imag;
    r.imag = a.real * b.imag + a.imag * b.real;
    return r;
}

static Complex complex_add(Complex a, Complex b) {
    Complex r;
    r.real = a.real + b.real;
    r.imag = a.imag + b.imag;
    return r;
}

#define SQRT2_2 0.7071067811865476

static const Complex H[2][2] = {
    {{SQRT2_2, 0}, {SQRT2_2, 0}},
    {{SQRT2_2, 0}, {-SQRT2_2, 0}},
};

static const Complex X[2][2] = {
    {{0, 0}, {1, 0}},
    {{1, 0}, {0, 0}},
};

static const Complex Z[2][2] = {
    {{1, 0}, {0, 0}},
    {{0, 0}, {-1, 0}},
};

static void apply_gate(const Complex gate[2][2], Complex state[2]) {
    Complex new0 = complex_add(
        complex_mult(gate[0][0], state[0]),
        complex_mult(gate[0][1], state[1])
    );
    Complex new1 = complex_add(
        complex_mult(gate[1][0], state[0]),
        complex_mult(gate[1][1], state[1])
    );
    state[0] = new0;
    state[1] = new1;
}

static void print_state(const char *label, const Complex state[2]) {
    printf("%s\n", label);
    printf("( %+f %+fi )|0⟩ + ( %+f %+fi )|1⟩\n",
           state[0].real, state[0].imag,
           state[1].real, state[1].imag);
    printf("\n");
}

static int measure(const Complex state[2], unsigned int *seed) {
    double prob0 = state[0].real * state[0].real
                 + state[0].imag * state[0].imag;
    *seed = (1103515245u * (*seed) + 12345u) & 0x7FFFFFFFu;
    double r = (double)(*seed) / (double)0x80000000u;
    if (r < prob0) return 0;
    else           return 1;
}

int main(void) {
    /* 1. 初始化为 |0⟩ = [1+0i, 0+0i] */
    Complex state[2] = {{1.0, 0.0}, {0.0, 0.0}};

    print_state("Initial state |0⟩:", state);

    /* 2. H 门: |0⟩ → |+⟩ = (|0⟩+|1⟩)/√2 */
    apply_gate(H, state);
    print_state("After H gate (superposition):", state);

    /* 3. X 门: |+⟩ → |+⟩ (本征态,不变) */
    apply_gate(X, state);
    print_state("After X gate (Pauli-X/NOT):", state);

    /* 4. Z 门: |+⟩ → |−⟩ = (|0⟩−|1⟩)/√2 */
    apply_gate(Z, state);
    print_state("After Z gate (Pauli-Z):", state);

    /* 5. H 门: |−⟩ → |1⟩ = [0, 1] */
    apply_gate(H, state);
    print_state("After second H gate:", state);

    /* 6. 100 次测量 */
    int counts[2] = {0, 0};
    unsigned int seed = 42u;

    for (int i = 0; i < 100; i++) {
        int outcome = measure(state, &seed);
        counts[outcome]++;
    }

    /* 7. 输出统计 */
    printf("Measurement results (%d trials):\n", 100);
    printf("|0⟩: %d (%.2f%%)\n",
           counts[0], counts[0] * 100.0 / 100);
    printf("|1⟩: %d (%.2f%%)\n",
           counts[1], counts[1] * 100.0 / 100);

    return 0;
}

完整程序输出:

Initial state |0⟩:
( +1.000000 +0.000000i )|0⟩ + ( +0.000000 +0.000000i )|1⟩

After H gate (superposition):
( +0.707107 +0.000000i )|0⟩ + ( +0.707107 +0.000000i )|1⟩

After X gate (Pauli-X/NOT):
( +0.707107 +0.000000i )|0⟩ + ( +0.707107 +0.000000i )|1⟩

After Z gate (Pauli-Z):
( +0.707107 +0.000000i )|0⟩ + ( -0.707107 +0.000000i )|1⟩

After second H gate:
( +0.000000 +0.000000i )|0⟩ + ( +1.000000 +0.000000i )|1⟩

Measurement results (100 trials):
|0⟩: 0 (0.00%)
|1⟩: 100 (100.00%)

核心逻辑解析:

  1. H 门产生叠加|0⟩ → |+⟩,α 和 β 都变为 1/√2 ≈ 0.707107
  2. X 门对 |+⟩ 无影响:因为 |+⟩ 的 α=β,交换后不变——|+⟩ 是 X 的本征态
  3. Z 门翻转相位:β 符号从正变负,但测量概率不变(|−β|² = |β|²
  4. H 门回到基态H|−⟩ = |1⟩(利用 H²=I,H|−⟩ = H·H|1⟩ = |1⟩)
  5. 测量确定性:最终状态是纯 |1⟩,prob0=0,100 次全部返回 |1⟩

对照检查complex_mult 的 imag 是 a.real*b.imag + a.imag*b.real(加号)吗?apply_gate 用了临时变量吗?measure0x80000000u 带了 u 后缀吗?seed 的初始值是 42u 吗?print_state 的格式字符串里有 %+f 吗?


课堂讨论

  1. apply_gate 中如果不使用临时变量,先计算并写回 state[0] 再计算 state[1],会发生什么?用 H 门作用在 |0⟩ 上跟踪状态变化,解释错误产生的具体过程。
  2. 如果改变门的作用顺序为 H→Z→X→H(与本题的 H→X→Z→H 不同),最终状态是什么?这说明了量子门的什么性质?
  3. 本题最终测量 100 次全部返回 |1⟩,这不是随机性"恰好"导致的——为什么?如果测量 |+⟩ 态(Step 1 后的状态),统计结果会怎样?
  4. 为什么本题使用硬编码 LCG 而不是 rand()seed = 42u 改变会发生什么?
  5. 验证恒等式 HXH = Z 和 HZH = X。它们揭示了 Hadamard 门的什么深层性质?
  6. 一个同学写 complex_mult 时虚部用了减号(a.real*b.imag - a.imag*b.real),但程序编译无错,测试 diff 失败。如何快速定位这个错误?

讨论答案

Q1: apply_gate 不用临时变量的错误追踪

以 H 门作用在 |0⟩ = [1, 0]ᵀ 为例:

正确 (使用临时变量):
  new0 = 0.707·1 + 0.707·0 = 0.707      (读 state[0]=1)
  new1 = 0.707·1 + (−0.707)·0 = 0.707   ( state[0]=1)
  写回: state[0]=0.707, state[1]=0.707 |+⟩

错误 (先写回 state[0]):
  state[0] = 0.707·1 + 0.707·0 = 0.707  (读 state[0]=1, 写回 0.707)
  state[1] = 0.707·0.707 + (−0.707)·0 = 0.5
             ^^^^^ 读到的 state[0] 已是 0.707 而非 1!
  结果: state[0]=0.707, state[1]=0.5 错误状态!

这是"写后读"数据竞争——单线程程序中也存在,因为两个表达式存在依赖关系。正确做法:计算 new0new1 时都读取原始的 state[0]state[1],计算完再一起写回。

Q2: 门顺序不同的结果——量子门不对易
H→X→Z→H|0⟩ = |1⟩  (本题顺序)
H→Z→X→H|0⟩ = |0⟩  (交换 X Z 的顺序)

验证:
  已知 HXH = Z, HZH = X H X Z 共轭互换
  
  HZXH|0⟩ = H·Z·X·H|0⟩
 X|0⟩ = |1⟩, H|1⟩ = |−⟩, Z|−⟩ = |+⟩, H|+⟩ = |0⟩
 回到 |0⟩

这是因为 X Z 不对易 (anti-commute):
  XZ = −ZX
  
量子门大多数不对易——顺序至关重要!
这不同于经典逻辑门 (AND、OR 可交换)。
Q3: 测量确定性与概率性

本题最终状态 |ψ₄⟩ = |1⟩ = [0, 1]ᵀ:

  • prob0 = 0²+0² = 0
  • r ∈ [0, 1) 永远 ≥ 0 → r < 0 永远不成立
  • → 100 次全部返回 |1⟩,确定性的,不是"恰好"

如果测量 |+⟩ = [1/√2, 1/√2]ᵀ(Step 1 后):

  • prob0 = 0.5
  • LCG 产生 [0,1) 上均匀分布的伪随机列
  • 预期 counts[0] ≈ 50, counts[1] ≈ 50
  • 具体数值取决于 seed 和序列——seed=42u 时特定结果可精确预测

关键理解:量子测量的概率性是本质性的(Born 规则),不是信息不完备。但纯态(α 或 β 为 0)时概率退化到确定性。

Q4: LCG vs rand() 的设计选择

rand() 在不同平台/实现下行为不同:

  • glibc (Linux): 内部 LCG,参数 (1103515245, 12345)
  • musl (Alpine): 不同算法
  • MSVC: 又一种算法

硬编码 LCG 的好处:

  1. 所有平台产生相同序列 → expected_output.txt 可精确预测
  2. 学员能直接看到随机数的生成公式,理解"伪随机"的本质
  3. seed = 42u 改变 → 随机序列不同 → 统计结果不同 → diff 测试失败
c
/* 如果 seed 初始值不是 42u,如 seed = 123u:
 *   LCG 从 123 开始产生完全不同序列
 *   → 即使最终状态相同 (纯 |1⟩),序列值不同
 *   → 输出可能也不同 (如输出格式包含 seed 值则 diff 失败)
 *
 * 如果 seed = 0:
 *   r 序列: 某特定序列
 *   但 seed=0 时 LCG 第一轮结果为 12345/2³¹
 *   不影响 |1⟩ 态测量 (r 总 ≥ 0)
 */
Q5: H 门作为基底转换器——Pauli 矩阵的对偶性

恒等式 HXH = Z:

HXH = 1/√2 [[1, 1]] [[0, 1]] 1/√2 [[1, 1]]
          [[1,-1]] [[1, 0]]      [[1,-1]]

    = 1/2 [[1, 1]] [[1,-1]]  = 1/2 [[1·1+1·1, 1·(-1)+1·(-1)]]
          [[1,-1]] [[1, 1]]        [[1·1+(-1)·1, 1·(-1)+(-1)·1]]

    = 1/2 [[2, -2]]  = [[1,  0]]  = Z  ✓
          [[0,  0]]    [[0, -1]]

恒等式 HZH = X(同理)。

深刻含义:Hadamard 门是 X 基底和 Z 基底之间的转换器:

  • 在 Z 基底(计算基 |0⟩,|1⟩)中:X 是比特翻转,Z 是相位翻转
  • 在 X 基底(|+⟩,|−⟩)中:角色互换——Z 变成比特翻转,X 变成相位翻转

这是量子力学中"基底变换"的完美示例——同一个物理操作在不同基底中看起来完全不同。H 门的作用相当于将坐标系旋转到 X-Z 平面的对角线。

Q6: complex_mult 符号错误定位方法
症状: 程序编译通过,但输出与 expected_output 不一致。

诊断步骤:

1. 独立测试 complex_mult:
   已知 (1+2i)*(3+4i) = (−5)+10i
   打印 complex_mult({1,2}, {3,4}) 的结果
   若得 (−5)+2i 虚部符号错误 (用了减号)

2. 观察错误传播:
   如果 complex_mult 符号错,H 门结果也会错:
   H|0⟩ = [0.707, 0.707] 正确
   但计算的 g₀₀*state[0] 就会偏掉
 连锁反应导致最终状态不是 |1⟩

3. 二分法定位:
   先检查 complex_mult 是否正确(独立测试)
   再检查 complex_add(简单,大概率对)
   再检查 apply_gate(用已知输入输出验证)
   最后检查 main 流程

4. 精确的复乘验证集:
   (0.707+0i)*(1+0i) = 0.707+0i
   (1+0i)*(0+1i) = 0+1i  (i*i = -1: 0+(-1) = -1+0i 只要不用减号)

课后练习

  1. 实现 Pauli-Y 门。Y = [[0,−i],[i,0]],是本题未使用的第三个 Pauli 矩阵。验证 Y|0⟩ = i|1⟩Y|1⟩ = −i|0⟩。注意:Y 门首次引入了需要虚部非零的 Complex 常量。

    知识点提示:Y 门的矩阵元素包含虚数 i,需要用 (Complex){0, ±1} 表示。Y|0⟩ 的结果 β = i(全虚数),验证 |β|² = 1 仍满足归一化。

    参考解答
    ex1_pauli_y.c
    c
    #include <math.h>
    #include <stdio.h>
    
    typedef struct { double real; double imag; } Complex;
    
    static Complex complex_mult(Complex a, Complex b) {
        Complex r;
        r.real = a.real * b.real - a.imag * b.imag;
        r.imag = a.real * b.imag + a.imag * b.real;
        return r;
    }
    
    static Complex complex_add(Complex a, Complex b) {
        Complex r;
        r.real = a.real + b.real;
        r.imag = a.imag + b.imag;
        return r;
    }
    
    static void apply_gate(const Complex gate[2][2], Complex state[2]) {
        Complex new0 = complex_add(
            complex_mult(gate[0][0], state[0]),
            complex_mult(gate[0][1], state[1])
        );
        Complex new1 = complex_add(
            complex_mult(gate[1][0], state[0]),
            complex_mult(gate[1][1], state[1])
        );
        state[0] = new0;
        state[1] = new1;
    }
    
    /* Pauli-Y: [[0, -i], [i, 0]] */
    static const Complex Y[2][2] = {
        {{0, 0}, {0, -1}},   /* row 0: [0, -i] */
        {{0, 1}, {0,  0}},   /* row 1: [i,  0] */
    };
    
    int main(void) {
        Complex state[2];
    
        /* Y|0⟩ = i|1⟩ = [0, i] */
        state[0] = (Complex){1.0, 0.0};
        state[1] = (Complex){0.0, 0.0};
        apply_gate(Y, state);
        printf("Y|0⟩ = (%.1f%+.1fi)|0⟩ + (%.1f%+.1fi)|1⟩\n",
               state[0].real, state[0].imag,
               state[1].real, state[1].imag);
        /* 预期: (0.0+0.0i)|0⟩ + (0.0+1.0i)|1⟩ = i|1⟩ */
    
        /* Y|1⟩ = -i|0⟩ = [-i, 0] */
        state[0] = (Complex){0.0, 0.0};
        state[1] = (Complex){1.0, 0.0};
        apply_gate(Y, state);
        printf("Y|1⟩ = (%.1f%+.1fi)|0⟩ + (%.1f%+.1fi)|1⟩\n",
               state[0].real, state[0].imag,
               state[1].real, state[1].imag);
        /* 预期: (0.0-1.0i)|0⟩ + (0.0+0.0i)|1⟩ = -i|0⟩ */
    
        /* Y² = I 验证 */
        state[0] = (Complex){1.0, 0.0};
        state[1] = (Complex){0.0, 0.0};
        apply_gate(Y, state);  /* Y|0⟩ = i|1⟩ */
        apply_gate(Y, state);  /* Y(i|1⟩) = i·(−i|0⟩) = −i²|0⟩ = |0⟩ */
        printf("Y²|0⟩ = (%.1f%+.1fi)|0⟩ + (%.1f%+.1fi)|1⟩\n",
               state[0].real, state[0].imag,
               state[1].real, state[1].imag);
        /* 预期: 回到 |0⟩ */
    
        return 0;
    }

    关键:Y 门的 (0,1) 位置是 −i → {0, −1};(1,0) 位置是 i → {0, 1}。验证 Y²=I——Y 门施加两次回到原态,乘以全局相位 −1 后归一到同一物理态。

  2. 验证酉矩阵保持归一化。编写 double norm(const Complex state[2]) 函数计算 |α|²+|β|²。在每次 apply_gate 后调用,验证其始终为 1.0。讨论浮点误差可能的范围。

    知识点提示:每次门操作后,|α|²+|β|² 应保持为 1.0。但浮点运算(特别是 1/√2 的乘法)会引入微小误差,通常 < 10^{-15}。

    参考解答
    ex2_norm_check.c
    c
    #include <math.h>
    #include <stdio.h>
    
    typedef struct { double real; double imag; } Complex;
    
    /* ... 此处省略 complex_mult, complex_add, apply_gate, H/X/Z 定义 ... */
    
    /* 计算量子态范数 |α|² + |β|² */
    static double norm(const Complex state[2]) {
        double mod0 = state[0].real * state[0].real
                    + state[0].imag * state[0].imag;
        double mod1 = state[1].real * state[1].real
                    + state[1].imag * state[1].imag;
        return mod0 + mod1;
    }
    
    int main(void) {
        Complex state[2] = {{1.0, 0.0}, {0.0, 0.0}};
        printf("Initial norm: %.16f\n", norm(state));  /* 1.0 */
    
        /* 施加各门并验证范数 */
        apply_gate(H, state);
        printf("After H: norm = %.16f\n", norm(state));
    
        apply_gate(X, state);
        printf("After X: norm = %.16f\n", norm(state));
    
        apply_gate(Z, state);
        printf("After Z: norm = %.16f\n", norm(state));
    
        apply_gate(H, state);
        printf("After H: norm = %.16f\n", norm(state));
    
        /* 验证归一化是否保持 */
        double final_norm = norm(state);
        if (fabs(final_norm - 1.0) < 1e-14)
            printf("Normalization preserved ✓ "
                   "(error: %e)\n", fabs(final_norm - 1.0));
        else
            printf("Normalization violated! "
                   "(error: %e)\n", fabs(final_norm - 1.0));
    
        return 0;
    }
    
    /* 预期输出:
     * Initial norm: 1.0000000000000000
     * After H: norm = 0.9999999999999999  (或 1.0000000000000002)
     * After X: norm = 1.0000000000000000
     * After Z: norm = 1.0000000000000000
     * After H: norm = 1.0000000000000000
     * Normalization preserved ✓ (error: ~1e-16)
     *
     * 注: H 门使用 1/√2 ≈ 0.7071067811865476 (约 16 位精度),
     * 平方后舍入误差在 ~1e-16 量级,完全可接受。
     */

    浮点误差分析:(0.7071067811865476)² ​≈ 0.5 精确到 ~1e-16,四次操作累积误差也 < 1e-14,远小于 double 精度极限。

  3. 实现通用旋转门。编写 void rx(double theta, Complex state[2]) 实现绕 X 轴的旋转门 Rx(θ) = [[cos(θ/2), −i·sin(θ/2)],[−i·sin(θ/2), cos(θ/2)]]。验证 Rx(π) = −iX(等价于 X 门乘以全局相位 −i)。

    知识点提示:需要 <math.h>cos()sin()。全局相位 −i 不影响物理可观测结果——这是量子力学中"物理态是射线"的体现。

    参考解答
    ex3_rx_gate.c
    c
    #include <math.h>
    #include <stdio.h>
    
    typedef struct { double real; double imag; } Complex;
    
    /* ... 省略 complex_mult, complex_add, apply_gate, print_state ... */
    
    /* Rx(θ) — 绕 X 轴旋转 θ 弧度
     * Rx(θ) = [[ cos(θ/2),    -i·sin(θ/2) ],
     *          [ -i·sin(θ/2),  cos(θ/2)   ]] */
    static void rx(double theta, Complex state[2]) {
        double c = cos(theta / 2.0);
        double s = sin(theta / 2.0);
    
        Complex gate[2][2] = {
            {{c, 0},  {0, -s}},   /* row 0: [cos(θ/2), -i·sin(θ/2)] */
            {{0, -s}, {c,  0}},   /* row 1: [-i·sin(θ/2), cos(θ/2)] */
        };
    
        apply_gate(gate, state);
    }
    
    int main(void) {
        Complex state[2];
    
        /* Rx(π) 应等价于 X 门 */
        printf("Rx(π) applied to |0⟩:\n");
        state[0] = (Complex){1.0, 0.0};
        state[1] = (Complex){0.0, 0.0};
        rx(M_PI, state);
        printf("  α = %.3f%+.3fi\n",
               state[0].real, state[0].imag);
        printf("  β = %.3f%+.3fi\n",
               state[1].real, state[1].imag);
        /* 预期: α = 0, β = 0-1i = -i
         *       X|0⟩ = [0, 1], 这里的 [0, -i] 差了一个全局相位 −i
         *       物理上等价——测量概率分别为 0 和 1 */
    
        /* Rx(π/2) 产生非平凡叠加态 */
        printf("\nRx(π/2) applied to |0⟩:\n");
        state[0] = (Complex){1.0, 0.0};
        state[1] = (Complex){0.0, 0.0};
        rx(M_PI / 2.0, state);
        /* 预期: α = 1/√2, β = -i/√2 (|+i⟩ 的转置共轭) */
        printf("  α = %.6f%+.6fi\n",
               state[0].real, state[0].imag);
        printf("  β = %.6f%+.6fi\n",
               state[1].real, state[1].imag);
        /* prob0 = 0.5, prob1 = 0.5 */
    
        return 0;
    }

    全局相位Rx(π) 给出的结果是 [0, −i]ᵀ,而 X 门给出 [0, 1]ᵀ。两者差了一个全局相位因子 −i。但测量概率 |β|² = |−i|² = 1 相同——全局相位不影响任何物理可观测结果。这是量子力学中"物理态是 Hilbert 空间中的射线(ray)"的体现。


参考资料

  • Nielsen & Chuang《Quantum Computation and Quantum Information》— 量子计算标准教材,第 1-2 章覆盖单量子比特和量子门
  • Michael A. Nielsen《Quantum Computing for the Very Curious》— 互动式量子计算入门
  • IBM Quantum Experience (quantum-computing.ibm.com) — 在线量子计算平台,可用 Qiskit 编写量量子电路
  • C99 标准 §7.3 — <complex.h> 复数算术库(本题手动实现的底层原理)
  • glibc rand() 源码 — LCG 参数 (1103515245, 12345, 2³¹) 的来源
  • Quantum Inspire — qubit visualization — Bloch 球交互式可视化工具

"I think I can safely say that nobody understands quantum mechanics." — Richard Feynman

Released under the MIT License.