Skip to main content

mingli_core/
gf2.rs

1//! S3 二进制格 (Z₂)^k + GF(2) 线性代数(家族 C 的代数石)。
2//!
3//! 覆盖易经卦(2⁶)、地占/Ifá 单图(2⁴)、Sikidy(GF(2) 矩阵)。核心运算:位↔索引、
4//! XOR(GF(2) 加法)、转置、奇偶校验。本模块用穷举测试**证明**地占「法官恒偶」与
5//! Sikidy「C15 恒偶」是同一条 GF(2) 奇偶校验定理。
6
7/// 一个 figure / 卦象 = 低 k 位的位向量(用 u16 承载,k≤16)。
8pub type Figure = u16;
9
10/// GF(2) 加法(逐位 XOR)。
11#[inline]
12#[must_use]
13pub fn xor(a: Figure, b: Figure) -> Figure {
14    a ^ b
15}
16
17/// 总点数奇偶:true = 偶(even / 双点多),即置位数为偶。
18#[inline]
19#[must_use]
20pub fn is_even(f: Figure) -> bool {
21    f.count_ones().is_multiple_of(2)
22}
23
24/// 把 `k` 个二进制位(低位在前)打包成 [`Figure`]。
25#[must_use]
26pub fn pack(bits: &[bool]) -> Figure {
27    bits.iter()
28        .enumerate()
29        .fold(0u16, |acc, (i, &b)| if b { acc | (1 << i) } else { acc })
30}
31
32/// 把 Figure 的低 `k` 位拆成位向量(低位在前)。
33#[must_use]
34pub fn unpack(f: Figure, k: usize) -> Vec<bool> {
35    (0..k).map(|i| (f >> i) & 1 == 1).collect()
36}
37
38/// 把 4 个「母图」(各 4 位)按 4×4 矩阵转置成 4 个「女图」。
39/// 女图 `d` 的第 `i` 位 = 母图 `i` 的第 `d` 位。地占 / Sikidy 共用此操作。
40#[must_use]
41pub fn transpose4(mothers: [Figure; 4]) -> [Figure; 4] {
42    let mut out = [0u16; 4];
43    for (d, slot) in out.iter_mut().enumerate() {
44        for (i, &m) in mothers.iter().enumerate() {
45            if (m >> d) & 1 == 1 {
46                *slot |= 1 << i;
47            }
48        }
49    }
50    out
51}
52
53/// 地占 shield chart:由 4 母图推出全部派生图。
54#[derive(Debug, Clone, Copy)]
55pub struct Shield {
56    /// 4 母图(随机起,每图 4 位)。
57    pub mothers: [Figure; 4],
58    /// 4 女图 = 母块转置。
59    pub daughters: [Figure; 4],
60    /// 4 侄图 = 母/女成对 XOR。
61    pub nieces: [Figure; 4],
62    /// 2 见证 `[右, 左]` = 侄成对 XOR。
63    pub witnesses: [Figure; 2],
64    /// 法官 = 两见证 XOR;**恒为偶图**(GF(2) 奇偶校验定理)。
65    pub judge: Figure,
66}
67
68/// 由 4 母图推出完整 shield chart(女/侄/见证/法官)。
69#[must_use]
70pub fn geomancy_shield(mothers: [Figure; 4]) -> Shield {
71    let d = transpose4(mothers);
72    let n = [
73        xor(mothers[0], mothers[1]),
74        xor(mothers[2], mothers[3]),
75        xor(d[0], d[1]),
76        xor(d[2], d[3]),
77    ];
78    let wr = xor(n[0], n[1]);
79    let wl = xor(n[2], n[3]);
80    Shield {
81        mothers,
82        daughters: d,
83        nieces: n,
84        witnesses: [wr, wl],
85        judge: xor(wr, wl),
86    }
87}
88
89/// Sikidy:由 4 随机母列(各 4 位)生成 16 列,返回创世者列 C15(0-based idx 14)。
90/// C5–C8 = 母块转置;C9–C16 = 逐列 XOR 树。
91#[must_use]
92pub fn sikidy_columns(mothers: [Figure; 4]) -> [Figure; 16] {
93    let mut c = [0u16; 16];
94    c[0..4].copy_from_slice(&mothers);
95    let t = transpose4(mothers);
96    c[4..8].copy_from_slice(&t);
97    c[8] = xor(c[6], c[7]); // C9 = C7⊕C8
98    c[9] = xor(c[4], c[5]); // C10 = C5⊕C6
99    c[10] = xor(c[2], c[3]); // C11 = C3⊕C4
100    c[11] = xor(c[0], c[1]); // C12 = C1⊕C2
101    c[12] = xor(c[8], c[9]); // C13 = C9⊕C10
102    c[13] = xor(c[10], c[11]); // C14 = C11⊕C12
103    c[14] = xor(c[12], c[13]); // C15 = C13⊕C14(创世者)
104    c[15] = xor(c[14], c[0]); // C16 = C15⊕C1
105    c
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn even_figures_count_is_eight() {
114        // 16 个 4 位 figure 中,偶图恰 8 个(popcount 0/2/4)。
115        let n = (0u16..16).filter(|&f| is_even(f)).count();
116        assert_eq!(n, 8);
117    }
118
119    #[test]
120    fn pack_unpack_roundtrip() {
121        for f in 0u16..64 {
122            assert_eq!(pack(&unpack(f, 6)), f);
123        }
124    }
125
126    #[test]
127    fn geomancy_judge_always_even() {
128        // 穷举全部 16⁴ = 65536 种母图组合,法官恒为偶图(GF(2) 奇偶校验定理)。
129        for combo in 0u32..(16 * 16 * 16 * 16) {
130            let m = [
131                (combo & 0xF) as u16,
132                ((combo >> 4) & 0xF) as u16,
133                ((combo >> 8) & 0xF) as u16,
134                ((combo >> 12) & 0xF) as u16,
135            ];
136            let s = geomancy_shield(m);
137            assert!(is_even(s.judge), "法官应恒为偶图, mothers={m:?}");
138        }
139    }
140
141    #[test]
142    fn sikidy_c15_always_even() {
143        // 同一条定理在 Sikidy 上的体现:创世者列 C15 恒为偶。
144        for combo in 0u32..(16 * 16 * 16 * 16) {
145            let m = [
146                (combo & 0xF) as u16,
147                ((combo >> 4) & 0xF) as u16,
148                ((combo >> 8) & 0xF) as u16,
149                ((combo >> 12) & 0xF) as u16,
150            ];
151            let c = sikidy_columns(m);
152            assert!(is_even(c[14]), "Sikidy C15 应恒为偶, mothers={m:?}");
153        }
154    }
155
156    /// 上面几条校验的都是**性质**:法官恒偶、转置是对合、打包能往返。把 `xor` 整个换成
157    /// 常量 0,或者把 `sikidy_columns` 整个换成全零数组,这些性质一条都不会破——
158    /// 0 是偶的,全零转置回来还是全零。所以这里钉住具体数值。
159    ///
160    /// 数值不是权威常数而是算术后果,可以逐位手验:母图写成 4×4 矩阵(行 = 母图、
161    /// 低位在左)是 `1000 / 1100 / 1110 / 1111`,转置后按列读出即 `1111 / 0111 / 0011 / 0001`,
162    /// 也就是 15 / 14 / 12 / 8。
163    #[test]
164    fn the_derived_figures_are_pinned_to_concrete_values() {
165        assert_eq!(xor(0b1010, 0b0110), 0b1100);
166        assert_eq!(xor(0b1111, 0b0000), 0b1111);
167        assert_eq!(pack(&[true, false, true, true]), 0b1101);
168        assert_eq!(unpack(0b1101, 4), vec![true, false, true, true]);
169
170        let mothers = [0b0001u16, 0b0011, 0b0111, 0b1111];
171        assert_eq!(transpose4(mothers), [15, 14, 12, 8]);
172
173        let s = geomancy_shield(mothers);
174        assert_eq!(s.daughters, [15, 14, 12, 8]);
175        assert_eq!(s.nieces, [2, 8, 1, 4]); // 母₁⊕母₂、母₃⊕母₄、女₁⊕女₂、女₃⊕女₄
176        assert_eq!(s.witnesses, [10, 5]);
177        assert_eq!(s.judge, 15);
178
179        assert_eq!(
180            sikidy_columns(mothers),
181            [1, 3, 7, 15, 15, 14, 12, 8, 4, 1, 8, 2, 5, 10, 15, 14]
182        );
183    }
184
185    /// 转置的定义(女图 `d` 的第 `i` 位 = 母图 `i` 的第 `d` 位)在这里用位向量重述一遍,
186    /// 与移位实现在全部 16⁴ 组母图上逐一对照——两条独立的写法算出同一张表。
187    #[test]
188    fn transpose_agrees_with_the_bit_vector_definition() {
189        for combo in 0u32..(16 * 16 * 16 * 16) {
190            let m = [
191                (combo & 0xF) as u16,
192                ((combo >> 4) & 0xF) as u16,
193                ((combo >> 8) & 0xF) as u16,
194                ((combo >> 12) & 0xF) as u16,
195            ];
196            let rows: Vec<Vec<bool>> = m.iter().map(|&f| unpack(f, 4)).collect();
197            let by_definition: [Figure; 4] =
198                core::array::from_fn(|d| pack(&(0..4).map(|i| rows[i][d]).collect::<Vec<_>>()));
199            assert_eq!(transpose4(m), by_definition, "mothers={m:?}");
200        }
201    }
202
203    #[test]
204    fn transpose_is_involution() {
205        // 转置两次还原。
206        for combo in [0x0123u32, 0xFEDC, 0xABCD] {
207            let m = [
208                (combo & 0xF) as u16,
209                ((combo >> 4) & 0xF) as u16,
210                ((combo >> 8) & 0xF) as u16,
211                ((combo >> 12) & 0xF) as u16,
212            ];
213            assert_eq!(transpose4(transpose4(m)), m);
214        }
215    }
216
217    use proptest::prelude::*;
218    proptest! {
219        #[test]
220        fn prop_pack_unpack_roundtrip(bits in prop::collection::vec(any::<bool>(), 0..16)) {
221            prop_assert_eq!(unpack(pack(&bits), bits.len()), bits);
222        }
223        #[test]
224        fn prop_is_even_matches_popcount_parity(f in any::<u16>()) {
225            prop_assert_eq!(is_even(f), f.count_ones() % 2 == 0);
226        }
227        #[test]
228        fn prop_transpose4_is_involution(combo in any::<u16>()) {
229            let m = [combo & 0xF, (combo >> 4) & 0xF, (combo >> 8) & 0xF, (combo >> 12) & 0xF];
230            prop_assert_eq!(transpose4(transpose4(m)), m);
231        }
232    }
233}