consistency 设计

设计目标

immut 和 mutable 用不同的存储和不同的内核把相同的核心运算实现了两遍,MatrixFn 又以惰性方式实现了第三遍。consistency 包检查这些实现表示的是同一套数学,从而让用户切换表示时结果不变,也让一个包中的内核优化无法悄悄改变语义。

数学背景

一致性即同态性质

设 ι:@immut.Matrix[T]→@mutable.Matrix[T]\iota : \texttt{@immut.Matrix[T]} \to \texttt{@mutable.Matrix[T]} 是保持形状和元素的转换。当 ι\iota 与运算 ω\omega 可交换时,称两个包在该运算上一致:

ι(ωimmut(A,B))=ωmutable(ιA,ιB).\iota\big(\omega_{\text{immut}}(A, B)\big) = \omega_{\text{mutable}}\big(\iota A, \iota B\big).

测试通过共同的观察手段(to_array、to_2d_array 或 to_string)比较两边,这些观察在已知形状的矩阵上是单射。

检验定律而非示例

除了一致性之外,测试还在两个包中检查代数定律:单位律、(AB)T=BTAT(AB)^{\mathsf T} = B^{\mathsf T} A^{\mathsf T}、乘积的结合律、分配律,以及 tr⁡(AT)=tr⁡(A)\operatorname{tr}(A^{\mathsf T}) = \operatorname{tr}(A)。转置乘积定律需要可交换的标量;测试使用 Int。

为何使用小整数

所有检查都使用 Int。整数运算是精确的环 Z/232Z\mathbb{Z}/2^{32}\mathbb{Z},即便溢出也是如此,因此每条定律都可以用 == 检验,每次失败都是真正的不一致。若用 Double,不同的求和顺序会合理地产生舍入差异(见 mutable 设计),测试就需要容差,而容差可能掩盖真正的缺陷。基于性质的测试用 quickcheck 随机抽取 2×22 \times 2 整数矩阵,并在每个样本上检查这些定律。

设计决策

独立的包

这些检查同时需要 immut 和 mutable,而两个包都不应依赖对方。由第三个包仅在测试中同时导入二者,可以保持依赖图整洁。它的测试是白盒测试(*_wbtest.mbt),因此可以使用不加限定的辅助函数名。

有文档说明的差异也要测试

在两个包有意不同的地方(例如 set 在 mutable 中原地修改,而在 immut 中返回新值),会有测试把差异固定下来,使其始终是一个决定而不是偶然。

正确性与不变量

对所有被测输入,该包断言:+、*、转置、迹、pow、行列式(整数输入)、构造函数和转换的结果相同;矩阵算术满足半环定律;以及 2×02 \times 0 这类退化形状具有文档所述的行为。

被否决的方案

  • 浮点一致性测试。 它们需要容差,测试的是舍入而不是语义;数值精度改在 mutable 内部测试。
  • 只测试示例。 随机输入能在作者没想到的值(例如负元素和零)上发现不一致。

边界

该包没有公开 API,也不发布供使用。它不测试只存在于一个包中的数值例程(逆矩阵、Cholesky、特征值),也不测试性能。