Skip to main content

mingli_core/
group.rs

1//! S5 有限集上的群作用 / 置换(横切 ⟂ 的代数石)。
2//!
3//! 统一三种"旋转/布置"机制:紫白飞星(Z₉ 的 ±k 作用)、紫微安星/月将加时(Z₁₂ 位移)、
4//! 以及抽样族的无放回置换(Fisher-Yates,见 [`crate::sampler`])。
5
6/// 循环群 `Z_n` 上的位移:`(start + k) mod n`(forward)或 `(start − k) mod n`(backward)。
7#[inline]
8#[must_use]
9pub fn shift(start: i64, k: i64, n: i64, forward: bool) -> i64 {
10    if forward {
11        (start + k).rem_euclid(n)
12    } else {
13        (start - k).rem_euclid(n)
14    }
15}
16
17/// 紫白飞星:洛书数 1..9 的阳顺阴逆飞布。给入中数 `center` 与步数 `k`,返回该步落数 1..9。
18/// 顺飞 = 数字递增、逆飞 = 递减(同一洛书路径)。
19#[inline]
20#[must_use]
21pub fn flying_star(center: i64, k: i64, forward: bool) -> i64 {
22    shift(center - 1, k, 9, forward) + 1
23}
24
25/// 把 1-based 的「从某宫起、数到第 m 步」化为地支宫位(0-based)。
26/// 用于紫微「寅起正月顺数生月、再逆数生时」等掐指类。
27#[inline]
28#[must_use]
29pub fn count_to(start: i64, steps: i64, n: i64, forward: bool) -> i64 {
30    shift(start, steps, n, forward)
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    #[test]
38    fn flying_star_wraps() {
39        // 八运 8 入中顺飞:8→9→1→2…
40        assert_eq!(flying_star(8, 0, true), 8);
41        assert_eq!(flying_star(8, 1, true), 9);
42        assert_eq!(flying_star(8, 2, true), 1);
43        assert_eq!(flying_star(8, 3, true), 2);
44        // 逆飞:8→7→6…
45        assert_eq!(flying_star(8, 1, false), 7);
46    }
47
48    #[test]
49    fn ziwei_ming_palace_shift() {
50        // 紫微命宫:寅(2)起正月顺数至 m 月、再逆数生时 hb(子=0 约定)。
51        // m=5(五月), hb=未(7) → 命宫应在亥(11)。
52        let after_month = count_to(2, 5 - 1, 12, true); // 顺数生月
53        let ming = count_to(after_month, 7, 12, false); // 逆数生时
54        assert_eq!(ming, 11); // 亥
55    }
56
57    use proptest::prelude::*;
58    proptest! {
59        #[test]
60        fn prop_shift_forward_then_back_is_identity(
61            start in 0i64..1000, k in 0i64..1000, n in 1i64..100,
62        ) {
63            let f = shift(start, k, n, true);
64            prop_assert!(f >= 0 && f < n);
65            prop_assert_eq!(shift(f, k, n, false), start.rem_euclid(n));
66        }
67        #[test]
68        fn prop_flying_star_in_1_9(center in 1i64..10, k in 0i64..1000, fwd in any::<bool>()) {
69            prop_assert!((1..=9).contains(&flying_star(center, k, fwd)));
70        }
71        #[test]
72        fn prop_count_to_in_range(
73            start in -100i64..100, steps in -100i64..100, n in 1i64..50, fwd in any::<bool>(),
74        ) {
75            let c = count_to(start, steps, n, fwd);
76            prop_assert!(c >= 0 && c < n);
77        }
78    }
79}