1use crate::GridTransform;
9
10pub const GRID_TRANSFORMS_D6: [GridTransform; 12] = [
20 GridTransform {
23 a: 1,
24 b: 0,
25 c: 0,
26 d: 1,
27 },
28 GridTransform {
30 a: 0,
31 b: -1,
32 c: 1,
33 d: 1,
34 },
35 GridTransform {
37 a: -1,
38 b: -1,
39 c: 1,
40 d: 0,
41 },
42 GridTransform {
44 a: -1,
45 b: 0,
46 c: 0,
47 d: -1,
48 },
49 GridTransform {
51 a: 0,
52 b: 1,
53 c: -1,
54 d: -1,
55 },
56 GridTransform {
58 a: 1,
59 b: 1,
60 c: -1,
61 d: 0,
62 },
63 GridTransform {
66 a: 1,
67 b: 1,
68 c: 0,
69 d: -1,
70 },
71 GridTransform {
73 a: 1,
74 b: 0,
75 c: -1,
76 d: -1,
77 },
78 GridTransform {
80 a: 0,
81 b: -1,
82 c: -1,
83 d: 0,
84 },
85 GridTransform {
87 a: -1,
88 b: -1,
89 c: 0,
90 d: 1,
91 },
92 GridTransform {
94 a: -1,
95 b: 0,
96 c: 1,
97 d: 1,
98 },
99 GridTransform {
101 a: 0,
102 b: 1,
103 c: 1,
104 d: 0,
105 },
106];
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use std::collections::HashSet;
112
113 fn compose(a: &GridTransform, b: &GridTransform) -> GridTransform {
114 GridTransform {
115 a: a.a * b.a + a.b * b.c,
116 b: a.a * b.b + a.b * b.d,
117 c: a.c * b.a + a.d * b.c,
118 d: a.c * b.b + a.d * b.d,
119 }
120 }
121
122 fn det(t: &GridTransform) -> i32 {
123 t.a * t.d - t.b * t.c
124 }
125
126 fn as_tuple(t: &GridTransform) -> (i32, i32, i32, i32) {
127 (t.a, t.b, t.c, t.d)
128 }
129
130 #[test]
131 fn all_twelve_distinct() {
132 let set: HashSet<_> = GRID_TRANSFORMS_D6.iter().map(as_tuple).collect();
133 assert_eq!(set.len(), 12);
134 }
135
136 #[test]
137 fn all_unimodular() {
138 for t in &GRID_TRANSFORMS_D6 {
139 let d = det(t);
140 assert!(d == 1 || d == -1, "det = {d} for {t:?}");
141 }
142 }
143
144 #[test]
145 fn rotations_det_plus_one() {
146 for t in &GRID_TRANSFORMS_D6[0..6] {
147 assert_eq!(det(t), 1, "rotation {t:?} should have det +1");
148 }
149 }
150
151 #[test]
152 fn reflections_det_minus_one() {
153 for t in &GRID_TRANSFORMS_D6[6..12] {
154 assert_eq!(det(t), -1, "reflection {t:?} should have det -1");
155 }
156 }
157
158 #[test]
159 fn rotation_order_six() {
160 let rot60 = &GRID_TRANSFORMS_D6[1];
161 let identity = &GRID_TRANSFORMS_D6[0];
162
163 let mut acc = *identity;
164 for k in 1..=6 {
165 acc = compose(&acc, rot60);
166 if k < 6 {
167 assert_ne!(
168 as_tuple(&acc),
169 as_tuple(identity),
170 "rot60^{k} should not be identity"
171 );
172 }
173 }
174 assert_eq!(
175 as_tuple(&acc),
176 as_tuple(identity),
177 "rot60^6 must be identity"
178 );
179 }
180
181 #[test]
182 fn reflections_are_involutions() {
183 for (i, t) in GRID_TRANSFORMS_D6[6..12].iter().enumerate() {
184 let t_sq = compose(t, t);
185 assert_eq!(
186 as_tuple(&t_sq),
187 as_tuple(&GRID_TRANSFORMS_D6[0]),
188 "reflection[{i}]^2 must be identity"
189 );
190 }
191 }
192
193 #[test]
194 fn closure_under_composition() {
195 let set: HashSet<_> = GRID_TRANSFORMS_D6.iter().map(as_tuple).collect();
196 for a in &GRID_TRANSFORMS_D6 {
197 for b in &GRID_TRANSFORMS_D6 {
198 let c = compose(a, b);
199 assert!(
200 set.contains(&as_tuple(&c)),
201 "product of {a:?} and {b:?} = {c:?} not in D6"
202 );
203 }
204 }
205 }
206
207 #[test]
208 fn rotations_match_successive_composition() {
209 let rot60 = &GRID_TRANSFORMS_D6[1];
210 let identity = &GRID_TRANSFORMS_D6[0];
211 let mut acc = *identity;
212 for (k, expected) in GRID_TRANSFORMS_D6.iter().enumerate().take(6) {
213 assert_eq!(as_tuple(&acc), as_tuple(expected), "rot60^{k} mismatch");
214 acc = compose(&acc, rot60);
215 }
216 }
217}