AI 如何做出数学发现【5】- AI 数学证明怎样做单元测试:从“看起来顺”到可否证工作流

系列:AI 如何做出数学发现(第 5 篇)

深度解读 OpenAI公开的GPT推演手稿《How the Ideas Came Together》,从失败路线、关键不变量与可复现实验出发,拆解 AI 参与数学发现的真实过程。涵盖球堆积、编码理论、复杂性、量子信息与极值组合,并严格区分发现笔记、正式证明、数值验证和形式化检查。

关键词:数学证明验证、AI 推理、单元测试、反例生成、符号计算、形式化证明

资源我已上传: How the Ideas Came Together:AI数学发现与证明路径重构手稿

前四篇分别出现了可行对象冲突、归一化边界、计算模型错配和全局 witness 不一致。本篇不再引入一条新的数学定理,而是把这些纠错动作整理成统一工作流:先让候选命题变得可攻击,再决定它是否值得投入完整证明、专家复核或形式化验证。

程序员看到 300 行没有测试的代码会不安,但面对 30 页推导,我们常常只做一件事:从头到尾再读一遍。问题是,人和模型在复读自己的论证时很容易沿用同一套错误假设。公式越流畅,越可能产生"应该没问题"的错觉。

数学当然不能被普通单元测试完全验证。有限样例通过,永远不能推出一个全称命题为真;浮点数一致,也不能替代误差界;CAS 输出 0,更没有检查你是否在合法区域里做了除法。

但反方向非常有价值:一个合法反例足以否定全称命题。 因此可以把研究工程中的许多纠错动作组织成 proof unit testing:先把候选命题、量词和假设注册下来,再用尽量独立的基线主动攻击它。

本文给出六类测试、一张断言卡和一个最小 claimcheck.py。配套程序刻意注册了四个错误命题和一个正向对照,运行结果是"找到 4 个反例;另 1 个仅在指定测试中未发现反例"。工具永远不会打印"证明通过"。

一、为什么"逐行读起来都对"仍可能整体错误

长证明常见的故障不是某一行明显算错,而是接口不匹配:

  • 上一段证明"对每个对象存在 witness",下一段却使用同一个统一 witness;
  • 一个公式对 circuit 成立,下一段把它当作 formula 的结论;
  • 渐近式在 n → ∞ n\to\infty n→∞ 成立,却被代入小参数做严格上界;
  • 独立抽样的方差被用于无放回抽样;
  • 复分析核的总质量不是 1,却被当作概率分布取期望;
  • CAS 在分母非零的隐含条件下化简,正文却忘了排除奇点。

这些错误都可能保持局部语法正确。单元测试要做的是把接口变成显式字段,然后从接口两端施压。

二、第一类:可行对象测试

这是成本最低、杀伤力最大的测试。

若候选定理声称 所有可行对象的目标值 ≤ U , \text{所有可行对象的目标值}\le U, 所有可行对象的目标值≤U,那就先找一个显式可行对象 x 0 x_0 x0,检查

value ⁡ ( x 0 ) ≤ U . \operatorname{value}(x_0)\le U. value(x0)≤U.

若声称下界 L L L,则找一个显式构造作上界基线,检查 L L L 是否超过已实现方案。

本系列第 1 篇"码长为 8 的二元码"例子就是标准模板。错误 Jacobi 递推导出的编码上界为

72.571428571 ... , 72.571428571\ldots, 72.571428571...,而长度 8 的偶校验码有 128 个码字,并满足所需距离条件。无需理解整条谱证明,这一个可行对象就足以否定候选上界。

text 复制代码
[FAIL] n=8 feasible-object check:
claimed upper bound=72.571428571;
even-parity code size=128

测试设计要点是先验独立:显式码应从定义直接验证,不要用待测递推本身判断它是否可行。

三、第二类:小参数穷举测试

很多命题在小规模可以直接枚举定义。设定义域 X n \mathcal X_n Xn 在 n ≤ n 0 n\le n_0 n≤n0 时足够小,可以比较:

definition_oracle ( x ) vs candidate_formula ( x ) . \text{definition\_oracle}(x) \quad\text{vs}\quad \text{candidate\_formula}(x). definition_oracle(x)vscandidate_formula(x).关键是枚举原定义,而不是枚举已经经过同一套可疑化简的模型。例如:

  • 码论中枚举所有二进制向量并直接算 Hamming 距离;
  • 图论中枚举边集并直接检查禁子图;
  • 线性系统中枚举所有 syndrome coset,而不是只测试随机满秩矩阵;
  • 小矩阵 Permanent 用排列定义求和,不调用待验证的递推。

第 4 篇 parity-lift 的正向对照就穷举了固定 3 × 4 3\times4 3×4 校验矩阵的所有 8 个 syndrome,再枚举二进制解和足够大的格点窗口。八个实例中都满足 min ⁡ z ∈ Λ H ∥ u − z ∥ 2 2 = min ⁡ { wt ⁡ ( x ) : H x = b } . \min_{z\in\Lambda_H}\|u-z\|_2^2 =\min\{\operatorname{wt}(x):Hx=b\}. z∈ΛHmin∥u−z∥22=min{wt(x):Hx=b}.状态只能写成:

text 复制代码
[PASS] small exhaustive positive control: all 8 syndromes agree

它增加可信度,却不是一般定理的证明。

四、第三类:边界与极限测试

边界是许多"差一个归一化"的错误集中地。建议至少覆盖:

  • 参数最小值与最大值;
  • 奇数/偶数、空集、单元素、零矩阵;
  • 分母趋近 0、重根、零特征值;
  • 概率趋近 0 或 1;
  • 两个极限交换顺序;
  • 归一化后的总质量。

球堆积 Mellin 路线中的调和测度给出一个很好的例子。某条边界的核总质量是

M σ = 1 − σ 2 , 0 < σ < 1 , M_\sigma=\frac{1-\sigma}{2}, \qquad0<\sigma<1, Mσ=21−σ,0<σ<1,而不是 1。若直接把未归一化核称为"概率密度",在 σ ↑ 1 \sigma\uparrow1 σ↑1 时会丢掉一个趋于 0、却参与主指数计算的因子。测试器选择 σ = 0.2 , 0.5 , 0.8 \sigma=0.2,0.5,0.8 σ=0.2,0.5,0.8,立即得到质量 0.4 , 0.25 , 0.1 0.4,0.25,0.1 0.4,0.25,0.1,从而否定"已归一化为概率测度"的候选命题。

边界测试不只代数代入。涉及极限时还应记录:哪个变量先趋于无穷?误差是否一致?支配函数是否依赖参数?端点是否仍在定义域?

五、第四类:依赖结构测试

相同的一维边缘分布,不代表联合结构相同。常见偷换包括:

  • 有放回与无放回抽样;
  • 独立随机变量与共享随机种子的变量;
  • circuit 中共享子表达式与 formula 中复制子树;
  • 对每个参数单独选择 witness 与预先固定一个 witness;
  • 多个系数由同一变量生成,却被当作代数独立。

测试器注册了一个故意错误的命题:"无放回抽样可直接使用独立 Bernoulli 和的方差。"总体大小 N = 10 N=10 N=10,其中 K = 4 K=4 K=4 个成功项,抽取 n = 5 n=5 n=5 个。独立模型给出 n p ( 1 − p ) = 1.2 , n p(1-p)=1.2, np(1−p)=1.2,超几何分布则带有限总体修正: n p ( 1 − p ) N − n N − 1 = 2 3 . n p(1-p)\frac{N-n}{N-1}=\frac23. np(1−p)N−1N−n=32.两者期望相同、方差不同。只检查均值完全发现不了这个问题。

依赖测试的实用写法是给随机源和共享关系编号: X i = f i ( U , V i ) X_i=f_i(U,V_i) Xi=fi(U,Vi) 中哪些变量共享 U U U?抽样空间是 product measure、条件分布还是固定总数的 slice?图画清楚后,许多错误"独立性"会自己暴露。

六、第五类:量词测试

量词错误往往不会触发数值异常,却会让整条证明断裂。最常见的是 ∀ x ∃ y P ( x , y ) \forall x\ \exists y\ P(x,y) ∀x ∃y P(x,y)被误写成 ∃ y ∀ x P ( x , y ) . \exists y\ \forall x\ P(x,y). ∃y ∀x P(x,y).第 4 篇 GapCVP 归约中,每种 table type τ \tau τ 可以有自己的 good sample set P τ P_\tau Pτ: ∀ τ ∃ P τ . \forall\tau\ \exists P_\tau. ∀τ ∃Pτ.如果程序数据结构只允许一个全局 good_points,实现已经悄悄假设了更强命题。量词测试可以不做复杂计算,只需检查 witness 的生命周期:它在循环外构造还是循环内构造?是否依赖当前对象?进入下一段前依赖关系有没有被遗忘?

建议把自然语言"任选""存在""统一地""几乎处处""对充分大"全部翻译为带依赖的签名。例如:

text 复制代码
good_set: TableType -> Set[Point]

text 复制代码
good_set: Set[Point]

是两种完全不同的证明接口。

七、第六类:模型对照测试

同一对象放进不同模型,结论可能完全不同。模型测试不是比较两个数值,而是列出允许的操作:

维度 模型 A 模型 B 必查接口
代数计算 circuit DAG formula tree 中间结果能否共享
除法 division-free valid division 分母何处非零
量子策略 tensor product commuting operator 状态空间与可交换条件
动力系统 连续/拓扑作用 measurable action witness 的正则性
概率 独立乘积 无放回/条件分布 协方差结构

审计 Permanent formula 的 valid-division 证明时,一个极小但致命的候选假设是:把一元 Möbius wrapper 的非零射影矩阵都当成可逆矩阵。反例是 ( 0 0 0 1 ) , \begin{pmatrix}0&0\\0&1\end{pmatrix}, (0001),

它非零且奇异,却表示合法常值映射 u ↦ 0 u\mapsto0 u↦0。测试器检查行列式为 0、分母恒为 1,立刻否定"每个有效非零 wrapper 都可逆"。

六类测试从不同方向攻击候选结论。覆盖面比同一种随机测试重复一万次更重要。

八、断言卡:先把待测命题结构化

一个可运行测试至少需要以下字段:

yaml 复制代码
id: C03
claim: 有效除法公式中的每个非零一元包装矩阵都可逆
quantifiers: 对每条不含标记变量分支的有效一元链
assumptions:
  - 矩阵按比例视为射影表示
baseline: 构造非零奇异矩阵并检查其有理映射是否有效
oracle: denominator_nonzero_on_domain && expression_defined
tolerance: exact
status: untested

我建议再补四项:

  • source:命题来自哪一页、哪条 lemma 或哪次模型输出;
  • dependency:依赖哪些尚未确认的命题;
  • domain_generator:怎样生成满足定义的对象;
  • counterexample:失败时保存最小可复现实例。

测试、专家复核和形式化检查应分别记录;任何一条轴都不能被一个含混的 passed 取代。

更稳妥的做法不是把所有证据压成一条线性状态机,而是维护三条状态轴: test_status ∈ { untested , tested-no-counterexample , counterexample-found } , review_status ∈ { not-reviewed , expert-reviewed } , formal_status ∈ { not-formalized , formally-checked } . \begin{aligned} \texttt{test\_status}&\in \{\texttt{untested},\texttt{tested-no-counterexample},\texttt{counterexample-found}\},\\ \texttt{review\_status}&\in \{\texttt{not-reviewed},\texttt{expert-reviewed}\},\\ \texttt{formal\_status}&\in \{\texttt{not-formalized},\texttt{formally-checked}\}. \end{aligned} test_statusreview_statusformal_status∈{untested,tested-no-counterexample,counterexample-found},∈{not-reviewed,expert-reviewed},∈{not-formalized,formally-checked}.

只要 test_status 记录了 counterexample-found,命题本身就进入 refuted;而 tested-no-counterexample 不会自动改变另外两条轴。专家复核能检查建模、意义和遗漏的假设,形式化系统检查已编码命题及其推导,两者覆盖范围不同,也不应互相覆盖。任何状态都不应被简写为 passed,否则传播到摘要和新闻稿时极易变成"已证明"。

九、数值测试必须携带误差模型

对浮点结果使用固定的 abs(a-b)<1e-9 并不专业。容差至少应考虑:

∣ f ^ − f ∣ ≤ E r o u n d + E t r u n c + E d i s c + E s t a t . |\widehat f-f| \le E_{\mathrm{round}}+E_{\mathrm{trunc}}+E_{\mathrm{disc}}+E_{\mathrm{stat}}. ∣f −f∣≤Eround+Etrunc+Edisc+Estat.

  • 舍入误差与运算次数、条件数有关;
  • 截断误差来自无穷级数或积分区间;
  • 离散误差来自网格和步长;
  • 随机误差需要置信区间或其他概率保证;固定 seed 只保证可复现,不能替代误差界。

矩阵特征值测试应检查残差 ∥ A v ^ − λ ^ v ^ ∥ , \|A\widehat v-\widehat\lambda\widehat v\|, ∥Av −λ v ∥,

而不只是记录库函数返回值;判断残差是否足够小还应考虑矩阵和向量的尺度。病态问题可用区间算术、多精度或有理重构。测试失败还应区分:候选命题被反例否定,还是数值误差预算不足以判定。

十、CAS 测试的边界:化简为零不等于推导合法

符号计算适合:

  • 展开并比较有限多项式恒等式;
  • 精确求小矩阵行列式和因式分解;
  • 在显式假设下做消元;
  • 生成小规模反例。

但 CAS 通常不会替你审计上下文。例如 x 2 − 1 x − 1 = x + 1 \frac{x^2-1}{x-1}=x+1 x−1x2−1=x+1作为有理函数,等式两边表示同一个元素;作为逐点求值的函数,等式只在共同定义域 x ≠ 1 x\ne1 x=1 上成立。若原命题包含 x = 1 x=1 x=1,右侧有定义而左侧没有。再如 sqrt(x^2)=x 需要实数域和 x ≥ 0 x\ge0 x≥0,在复数域或负实数上都不成立。

因此每个符号测试要记录 domain、nonzero assumptions 和 branch conventions。最稳妥的做法是让 CAS 负责代数内核,让独立代码负责生成满足定义域的测试点和检查前提。

十一、property-based testing:让程序寻找反例

例子测试由人指定输入;property-based testing 从定义生成大量对象,并在失败后缩小反例。一个数学对象生成器应先保证结构合法,再向候选性质施压:

python 复制代码
for object in generate_valid_objects(seed):
    assert definition_oracle(object)
    if not candidate_claim(object):
        save(shrink(object))

这里最容易犯的错误是生成器只覆盖"容易的正规对象"。例如随机矩阵几乎总满秩,永远测不到奇异边界;连续采样几乎不会命中重根;均匀随机图很少覆盖极端度序列。因此生成策略应混合:随机实例、边界实例、对称实例、退化实例和已知 extremizer。

测试预算也不应只写"随机 1000 次",而要写覆盖目标:所有 n ≤ 8 n\le8 n≤8、所有秩、所有奇偶类、所有分母零型、每个模型分支至少一个样例。

十二、claimcheck.py 的设计与输出

配套脚本把每条命题表示为:

python 复制代码
Claim(
    claim_id,
    statement,
    quantifiers,
    assumptions,
    baseline,
    tests,
)

每个测试返回 Observation(name, passed, detail)。报告生成器有意只使用两个结论:

  • 发现反例
  • 指定测试未发现反例

claimcheck.py完整Python代码如下:

python 复制代码
"""A minimal claim-level testing harness for mathematical.

The tool searches only the explicitly registered finite test space.  Therefore
its positive status is worded "no counterexample in specified tests", never
"proved".  Run:

    python -B 05_claimcheck.py
    python -B 05_claimcheck.py --output ../reports/05-claimcheck-report.md

Dependency: NumPy (for the Jacobi eigenvalue test).
"""

from __future__ import annotations

import argparse
from dataclasses import dataclass
from itertools import product
import math
from pathlib import Path
from typing import Callable

import numpy as np


@dataclass(frozen=True)
class Observation:
    name: str
    passed: bool
    detail: str


@dataclass(frozen=True)
class Claim:
    claim_id: str
    statement: str
    quantifiers: str
    assumptions: tuple[str, ...]
    baseline: str
    tests: tuple[Callable[[], Observation], ...]


def wrong_jacobi_bound() -> Observation:
    n, k, ell, s = 8, 1, 7, 0.5
    matrix = np.zeros((ell - k + 1, ell - k + 1))
    for offset, j in enumerate(range(k, ell)):
        coefficient = math.sqrt((j - k + 1) * (n - j - k)) / n
        matrix[offset, offset + 1] = coefficient
        matrix[offset + 1, offset] = coefficient
    eigenvalue = float(np.linalg.eigvalsh(matrix)[-1])
    dimension = sum(math.comb(n, j) for j in range(k, ell + 1))
    multiplicity = math.comb(n, k) - math.comb(n, k - 1)
    claimed_upper_bound = (1 - s) / (eigenvalue - s) * dimension / multiplicity
    feasible_even_parity_code = 2 ** (n - 1)
    passed = claimed_upper_bound + 1e-9 >= feasible_even_parity_code
    return Observation(
        "n=8 feasible-object check",
        passed,
        f"claimed upper bound={claimed_upper_bound:.9f}; "
        f"even-parity code size={feasible_even_parity_code}",
    )


def harmonic_mass_normalization() -> Observation:
    sigmas = (0.2, 0.5, 0.8)
    masses = tuple((1 - sigma) / 2 for sigma in sigmas)
    passed = all(abs(mass - 1.0) < 1e-12 for mass in masses)
    return Observation(
        "boundary/limit normalization check",
        passed,
        "sigma -> actual total mass: "
        + ", ".join(f"{sigma:.1f}->{mass:.3f}" for sigma, mass in zip(sigmas, masses)),
    )


def singular_projective_wrapper() -> Observation:
    # [[0,0],[0,1]] is nonzero and singular, but represents u -> 0/1.
    matrix = ((0, 0), (0, 1))
    determinant = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]
    nonzero = any(value != 0 for row in matrix for value in row)
    denominator_at_samples = tuple(matrix[1][0] * u + matrix[1][1] for u in (-2, 0, 3))
    represents_valid_map = nonzero and all(value != 0 for value in denominator_at_samples)
    # The tested claim says every valid nonzero wrapper must be invertible.
    passed = (not represents_valid_map) or determinant != 0
    return Observation(
        "model-comparison check",
        passed,
        f"matrix={matrix}; det={determinant}; valid constant map={represents_valid_map}",
    )


def without_replacement_variance() -> Observation:
    population, successes, draws = 10, 4, 5
    p = successes / population
    independent = draws * p * (1 - p)
    actual = independent * (population - draws) / (population - 1)
    passed = abs(independent - actual) < 1e-12
    return Observation(
        "dependency-structure check",
        passed,
        f"independent variance={independent:.6f}; "
        f"without-replacement variance={actual:.6f}",
    )


def exhaustive_parity_lift_control() -> Observation:
    h = (
        (1, 1, 0, 0),
        (0, 1, 1, 0),
        (0, 0, 1, 1),
    )

    def syndrome(x: tuple[int, ...]) -> tuple[int, ...]:
        return tuple(sum(a * b for a, b in zip(row, x)) % 2 for row in h)

    failures: list[str] = []
    all_binary = list(product((0, 1), repeat=4))
    lattice = [
        z for z in product(range(-2, 3), repeat=4)
        if syndrome(tuple(value % 2 for value in z)) == (0, 0, 0)
    ]
    for b in product((0, 1), repeat=3):
        solutions = [x for x in all_binary if syndrome(x) == b]
        if not solutions:
            continue
        min_weight = min(sum(x) for x in solutions)
        target = solutions[0]
        min_distance = min(sum((u - z_i) ** 2 for u, z_i in zip(target, z)) for z in lattice)
        if min_distance != min_weight:
            failures.append(f"b={b}: weight={min_weight}, distance^2={min_distance}")
    return Observation(
        "small exhaustive positive control",
        not failures,
        "all 8 syndromes agree" if not failures else "; ".join(failures),
    )


CLAIMS = (
    Claim(
        "C01",
        "旧 Jacobi 系数给出的 LP 表达式是所有二元码大小的上界。",
        "对所有 n,k,L,s 以及满足距离条件的二元码。",
        ("使用旧递推的非对称归一化", "Perron 根代入同一上界公式"),
        "先构造满足条件的显式码,再比较声称的上界。",
        (wrong_jacobi_bound,),
    ),
    Claim(
        "C02",
        "Mellin 边界核对每个 0<sigma<1 都已经归一化为概率测度。",
        "对所有 0<sigma<1。",
        ("未额外乘归一化常数",),
        "直接积分边界密度并检查总质量。",
        (harmonic_mass_normalization,),
    ),
    Claim(
        "C03",
        "有效除法公式中的每个非零一元 Mobius 包装矩阵都可逆。",
        "对每条不含被标记变量分支的有效一元链。",
        ("矩阵按比例视为射影表示",),
        "构造非零奇异矩阵并检查它是否仍表示合法有理映射。",
        (singular_projective_wrapper,),
    ),
    Claim(
        "C04",
        "无放回抽样可以直接使用独立 Bernoulli 和的方差。",
        "对任意总体大小、成功数和抽样数。",
        ("样本之间被当作独立",),
        "与超几何分布的有限总体修正比较。",
        (without_replacement_variance,),
    ),
    Claim(
        "C05",
        "小型二进制 syndrome 的最小重量等于 parity-lift 的最小平方距离。",
        "对固定 3x4 校验矩阵的所有可达 syndrome。",
        ("目标 u 满足 Hu=b", "格由 Hz=0 (mod 2) 定义"),
        "穷举二进制向量和足够大的最近格点窗口。",
        (exhaustive_parity_lift_control,),
    ),
)


def render_report() -> str:
    lines = [
        "# claimcheck 示例报告",
        "",
        "> 状态"指定测试未发现反例"只描述有限测试结果,不等于数学证明。",
        "",
    ]
    failed_claims = 0
    for claim in CLAIMS:
        observations = [test() for test in claim.tests]
        passed = all(observation.passed for observation in observations)
        if not passed:
            failed_claims += 1
        status = "指定测试未发现反例" if passed else "发现反例"
        lines.extend(
            [
                f"## {claim.claim_id} --- {status}",
                "",
                f"- 命题:{claim.statement}",
                f"- 量词:{claim.quantifiers}",
                f"- 假设:{';'.join(claim.assumptions)}",
                f"- 基线:{claim.baseline}",
                "",
            ]
        )
        for observation in observations:
            marker = "PASS" if observation.passed else "FAIL"
            lines.append(f"- [{marker}] {observation.name}:{observation.detail}")
        lines.append("")
    lines.extend(
        [
            "## 汇总",
            "",
            f"- 注册命题:{len(CLAIMS)}",
            f"- 发现反例:{failed_claims}",
            f"- 指定测试未发现反例:{len(CLAIMS) - failed_claims}",
            "- 证明状态:未由本工具判定",
            "",
        ]
    )
    return "\n".join(lines)


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()
    report = render_report()
    if args.output:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(report, encoding="utf-8")
        print(f"wrote {args.output}")
    else:
        print(report)


if __name__ == "__main__":
    main()

报告保留命题、量词、假设、基线和反例数值;截图应来自实际生成的 Markdown,不应由图片模型伪造终端文本。

本次真实输出汇总为:

text 复制代码
注册命题:5
发现反例:4
指定测试未发现反例:1
证明状态:未由本工具判定

四个失败分别击中可行对象、归一化边界、模型对照和依赖结构;正向对照穷举小型 parity-lift 的 8 个 syndrome。完整报告保存成 05-claimcheck-report.md

markup 复制代码
# claimcheck 示例报告

> 状态"指定测试未发现反例"只描述有限测试结果,不等于数学证明。

## C01 --- 发现反例

- 命题:旧 Jacobi 系数给出的 LP 表达式是所有二元码大小的上界。
- 量词:对所有 n,k,L,s 以及满足距离条件的二元码。
- 假设:使用旧递推的非对称归一化;Perron 根代入同一上界公式
- 基线:先构造满足条件的显式码,再比较声称的上界。

- [FAIL] n=8 feasible-object check:claimed upper bound=72.571428571; even-parity code size=128

## C02 --- 发现反例

- 命题:Mellin 边界核对每个 0<sigma<1 都已经归一化为概率测度。
- 量词:对所有 0<sigma<1。
- 假设:未额外乘归一化常数
- 基线:直接积分边界密度并检查总质量。

- [FAIL] boundary/limit normalization check:sigma -> actual total mass: 0.2->0.400, 0.5->0.250, 0.8->0.100

## C03 --- 发现反例

- 命题:有效除法公式中的每个非零一元 Mobius 包装矩阵都可逆。
- 量词:对每条不含被标记变量分支的有效一元链。
- 假设:矩阵按比例视为射影表示
- 基线:构造非零奇异矩阵并检查它是否仍表示合法有理映射。

- [FAIL] model-comparison check:matrix=((0, 0), (0, 1)); det=0; valid constant map=True

## C04 --- 发现反例

- 命题:无放回抽样可以直接使用独立 Bernoulli 和的方差。
- 量词:对任意总体大小、成功数和抽样数。
- 假设:样本之间被当作独立
- 基线:与超几何分布的有限总体修正比较。

- [FAIL] dependency-structure check:independent variance=1.200000; without-replacement variance=0.666667

## C05 --- 指定测试未发现反例

- 命题:小型二进制 syndrome 的最小重量等于 parity-lift 的最小平方距离。
- 量词:对固定 3x4 校验矩阵的所有可达 syndrome。
- 假设:目标 u 满足 Hu=b;格由 Hz=0 (mod 2) 定义
- 基线:穷举二进制向量和足够大的最近格点窗口。

- [PASS] small exhaustive positive control:all 8 syndromes agree

## 汇总

- 注册命题:5
- 发现反例:4
- 指定测试未发现反例:1
- 证明状态:未由本工具判定

运行方法:

bash 复制代码
python -B draft/code/05_claimcheck.py
python -B draft/code/05_claimcheck.py --output draft/reports/05-claimcheck-report.md

依赖 Python 3.10+ 与 NumPy。

十三、把测试插入 AI 数学工作流

一个实际可用的顺序是:

  1. 候选生成:模型提出 lemma、递推或参数选择;
  2. 断言提取:把每个关键跳步写成带量词的 Claim;
  3. 定义 oracle:尽量从原定义实现独立基线;
  4. 快速否证:先跑可行对象、边界、小参数与退化实例;
  5. 误差审计:为数值和随机测试写明误差预算;
  6. 依赖审计:画出 witness、随机源和共享子表达式;
  7. 证明重写:将通过测试但尚未证明的命题交给人工推导;
  8. 独立审查:让另一位研究者或另一套系统从源定义检查;
  9. 形式化:对高价值核心 lemma 进入 Lean/Coq/Isabelle;
  10. 回归测试:证明修订后重跑所有历史反例。

图 05-5 从下到上通常意味着单次检查成本和形式严格性提高,不代表所有问题都存在统一的可信度排序;专家审查与形式化验证覆盖不同风险。

测试特别适合作为"前置过滤器":它能在专家花数天读证明前,用几秒到几分钟淘汰明显错误路线。形式化证明则位于另一端,要求完整定义、lemma 库和可信内核。中间仍需要数学家的概念判断:为什么这个不变量正确?哪些情况应当分开?证明是否回答了原问题?

十四、发布数学结果前的最小检查表

  • 命题的所有量词是否写出,渐近变量和极限顺序是否明确?
  • 是否有显式可行对象或已知基线可做 sanity check?
  • 小参数测试是否直接按定义,而非复用候选公式?
  • 是否覆盖空、零、奇异、重根、端点和最低维数?
  • 随机量是否真的独立,witness 是否允许依赖当前对象?
  • circuit/formula、离散/连续、tensor/commuting 等模型有没有混用?
  • 数值结论是否携带条件数、截断和统计误差?
  • CAS 使用了哪些非零、实数域或分支假设?
  • 报告是否区分"发现反例""未发现反例""人工证明""形式化证明"?
  • 修订后的命题是否重新运行旧反例作为回归测试?

结语:测试的目标不是证明,而是让错误更便宜

Proof unit testing 不会把数学研究变成普通软件工程。无限对象、全称量词和概念创新决定了证明仍需要演绎论证。但测试能改变错误的成本结构:让一个模型错误在 2 × 2 2\times2 2×2 奇异矩阵上暴露,而不是藏到 40 页末尾;让一个上界错误被码长为 8 的显式码击穿,而不是等同行评审;让量词依赖进入数据结构,而不是只留在作者脑中。

对 AI 生成的数学尤其如此。模型很擅长延续局部模式,也因此可能把一个错误接口延续得异常流畅。最有效的补偿不是要求它"再仔细想想",而是给结论安排独立、具体、可运行的否证通道。---

证据边界

  • Walkthrough:六类测试综合了前四篇中的失败路线与反例模式;分类本身是本文的研究工程整理,不是原 PDF 中的一条定理。
  • Paper:过程监督、形式化验证和 property-based testing 的参考资料提供方法背景,但不推出"测试可以证明任意数学命题"。
  • Computationclaimcheck.py 的 5 条命题与输出已在本地运行;正向结果只覆盖脚本注册的有限测试集。
  • Formal/Review指定测试未发现反例 不蕴含命题为真;形式化验证与专家审查分别记录,不能由 NumPy、CAS 或随机测试替代。

参考资料

  1. How the Ideas Came Together(OpenAI,PDF)
  2. Our First Proof submissions(OpenAI)
  3. Lightman et al.: Let's Verify Step by Step(arXiv)
  4. Lean 4 Documentation
  5. [Hypothesis: Property-Based Testing for Python

手稿部分相关页面截图:

相关推荐
giszz1 小时前
【WorkBuddy专栏72】AI 有了自己的邮箱——WorkBuddy Agent Mail 从开通到自动化完全指南
人工智能·企业微信
@Mr_LiuYang1 小时前
《深入理解 AI Agent:设计原理与工程实践 》实验2-2 2-7 大模型注意力权重分布可视化
人工智能·大模型·agent·注意力机制
Seven971 小时前
AI杂谈:别再问AI会不会替代你,先看你是不是驾驶员
人工智能·后端
七夜zippoe2 小时前
OpenClaw 测试策略实战:AI Agent 自动化测试体系搭建与落地
人工智能·ai·自动化·agent·测试体系·openclaw
满怀冰雪2 小时前
20-卷积神经网络基础:用 Paddle 构建 CNN
人工智能·深度学习·cnn·paddle
(轻舟已过万重山)2 小时前
第40章 Spring AI 实战:企业级 AI 应用架构
人工智能·spring·架构
zzm6282 小时前
WSDM 2018论文精读:基于多关系学习与路径约束的商品替代互补关系挖掘
人工智能·学习
aneasystone本尊2 小时前
学习 Headroom 的 CCR 可逆压缩
人工智能