algebraeon_groups/composition_table/
partition.rs1use std::collections::{BTreeSet, HashSet};
2
3use super::group::*;
4use super::subset::*;
5
6#[derive(Clone, Debug)]
7pub struct PartitionState {
8 classes: Vec<BTreeSet<usize>>, lookup: Vec<usize>, }
11
12impl PartitionState {
13 pub fn check_state(&self, group: &Group) -> Result<(), &'static str> {
14 let mut accounted_elems: HashSet<usize> = HashSet::new();
16 for class in &self.classes {
17 if class.len() == 0 {
18 return Err("partition contains an empty set");
19 }
20 for x in class {
21 if accounted_elems.contains(x) {
22 return Err("partition contains duplicate elements");
23 }
24 accounted_elems.insert(*x);
25 }
26 }
27 if accounted_elems.len() != group.size() {
28 return Err("partition is missing some elements");
29 }
30
31 if !(self.lookup.len() == group.size()) {
33 return Err("partition lookup has the wrong length");
34 }
35 for (elem, class_idx) in self.lookup.iter().enumerate() {
36 if !(*class_idx < self.classes.len()) {
37 return Err("partition lookup index is bigger partition size");
38 }
39 if !self.classes[*class_idx].contains(&elem) {
40 return Err("partition lookup points to wrong partition set");
41 }
42 }
43 Ok(())
44 }
45
46 pub fn new_unchecked(classes: Vec<BTreeSet<usize>>, lookup: Vec<usize>) -> Self {
47 Self { classes, lookup }
48 }
49
50 pub fn project(&self, x: usize) -> &BTreeSet<usize> {
51 &self.classes[self.lookup[x]]
52 }
53}
54
55pub struct Partition<'a> {
56 pub group: &'a Group,
57 pub state: PartitionState,
58 }
61
62impl<'a> PartialEq for Partition<'a> {
63 fn eq(&self, other: &Self) -> bool {
64 let grp = self.group;
65 if !std::ptr::eq(grp, other.group) {
66 return false;
67 }
68 let n = self.size();
69 if n != other.size() {
70 return false;
71 }
72 let mut f: Vec<Option<usize>> = vec![None; n];
76 for i in 0..grp.size() {
77 match f[self.state.lookup[i]] {
78 Some(expected_other_lookup_i) => {
79 if expected_other_lookup_i != other.state.lookup[i] {
80 return false;
81 }
82 }
83 None => {
84 f[self.state.lookup[i]] = Some(other.state.lookup[i]);
85 }
86 }
87 }
88 true
89 }
90}
91
92impl<'a> Eq for Partition<'a> {}
93
94impl<'a> Partition<'a> {
95 pub fn check_state(&self) -> Result<(), &'static str> {
96 match self.state.check_state(self.group) {
97 Err(msg) => {
98 return Err(msg);
99 }
100 Ok(()) => {}
101 };
102
103 Ok(())
104 }
105
106 pub fn size(&self) -> usize {
107 self.state.classes.len()
108 }
109
110 pub fn is_left_cosets(&self) -> bool {
111 let ident_class = Subset::new_unchecked(
112 self.group,
113 self.state.classes[self.state.lookup[self.group.ident()]].clone(),
114 );
115 match ident_class.to_subgroup() {
116 Some(ident_subgroup) => &ident_subgroup.left_cosets() == self,
117 None => false,
118 }
119 }
120
121 pub fn is_right_cosets(&self) -> bool {
122 let ident_class = Subset::new_unchecked(
123 self.group,
124 self.state.classes[self.state.lookup[self.group.ident()]].clone(),
125 );
126 match ident_class.to_subgroup() {
127 Some(ident_subgroup) => &ident_subgroup.right_cosets() == self,
128 None => false,
129 }
130 }
131
132 pub fn is_congruence(&self) -> bool {
133 self.is_left_cosets() && self.is_right_cosets()
134 }
135
136 pub fn from_subsets(group: &'a Group, subsets: Vec<BTreeSet<usize>>) -> Result<Self, ()> {
137 let classes = subsets;
138 let mut lookup = vec![0; group.size()];
139 for (class_idx, class) in classes.iter().enumerate() {
140 for x in class {
141 if !(*x < group.size()) {
142 return Err(());
143 }
144 lookup[*x] = class_idx;
145 }
146 }
147 let partition = Self {
148 group,
149 state: PartitionState { classes, lookup },
150 };
151 match partition.check_state() {
152 Ok(_) => {}
153 Err(_) => {
154 return Err(());
155 }
156 }
157 Ok(partition)
158 }
159}
160
161pub struct Congruence<'a> {
162 pub partition: Partition<'a>,
163}
164
165impl<'a> Congruence<'a> {
166 pub fn check_state(&self) -> Result<(), &'static str> {
167 match self.partition.check_state() {
168 Ok(()) => {}
169 Err(msg) => {
170 return Err(msg);
171 }
172 }
173
174 match self.partition.is_congruence() {
175 false => return Err("congruence is not a congruence"),
176 true => {}
177 }
178
179 Ok(())
180 }
181
182 pub fn size(&self) -> usize {
183 self.partition.size()
184 }
185
186 pub fn quotient_group(&self) -> Group {
187 let n = self.size();
188
189 Group::new_unchecked(
190 n,
191 self.partition.state.lookup[self.partition.group.ident()],
192 {
193 let mut inv = vec![0; n];
194 for i in 0..n {
195 inv[i] = self.partition.state.lookup[self
196 .partition
197 .group
198 .inv(*self.partition.state.classes[i].iter().next().unwrap())];
199 }
200 inv
201 },
202 {
203 let mut mul = vec![vec![0; n]; n];
204 for i in 0..n {
205 for j in 0..n {
206 mul[i][j] = self.partition.state.lookup[self.partition.group.mul(
207 *self.partition.state.classes[i].iter().next().unwrap(),
208 *self.partition.state.classes[j].iter().next().unwrap(),
209 )];
210 }
211 }
212 mul
213 },
214 None,
215 None,
216 )
217 }
218}
219
220#[cfg(test)]
221mod partition_tests {
222 use super::*;
223
224 #[test]
225 fn partition_check_bad_state() {
226 let grp = examples::cyclic_group_structure(6);
227
228 let p = PartitionState {
230 classes: vec![
231 vec![0, 1, 2, 3].into_iter().collect(),
232 vec![4, 5, 6].into_iter().collect(),
233 ],
234 lookup: vec![0, 0, 0, 0, 1, 1, 1],
235 };
236 match p.check_state(&grp) {
237 Ok(()) => assert!(false),
238 Err(_) => {}
239 }
240
241 let p = PartitionState {
243 classes: vec![
244 vec![0, 2].into_iter().collect(),
245 vec![3, 5].into_iter().collect(),
246 ],
247 lookup: vec![0, 0, 0, 1, 1, 1],
248 };
249 match p.check_state(&grp) {
250 Ok(()) => assert!(false),
251 Err(_) => {}
252 }
253
254 let p = PartitionState {
256 classes: vec![
257 vec![0, 1, 2, 3].into_iter().collect(),
258 vec![2, 3, 4, 5].into_iter().collect(),
259 ],
260 lookup: vec![0, 0, 0, 0, 1, 1],
261 };
262 match p.check_state(&grp) {
263 Ok(()) => assert!(false),
264 Err(_) => {}
265 }
266
267 let p = PartitionState {
269 classes: vec![
270 vec![0, 1, 2].into_iter().collect(),
271 vec![3, 4, 5].into_iter().collect(),
272 ],
273 lookup: vec![0, 0, 0, 1, 1, 2],
274 };
275 match p.check_state(&grp) {
276 Ok(()) => assert!(false),
277 Err(_) => {}
278 }
279
280 let p = PartitionState {
282 classes: vec![
283 vec![0, 1, 2].into_iter().collect(),
284 vec![3, 4, 5].into_iter().collect(),
285 ],
286 lookup: vec![0, 0, 1, 1, 1, 1],
287 };
288 match p.check_state(&grp) {
289 Ok(()) => assert!(false),
290 Err(_) => {}
291 }
292 }
293
294 #[test]
295 fn congruence_check_state() {
296 let grp = examples::symmetric_group_structure(4);
297 for (sg, _gens) in grp.subgroups() {
298 match sg.to_normal_subgroup() {
299 None => {
300 let cong: Congruence<'_> = Congruence {
301 partition: sg.left_cosets(),
302 };
303 match cong.check_state() {
304 Ok(_) => panic!(),
305 Err(_) => {}
306 }
307 let cong: Congruence<'_> = Congruence {
308 partition: sg.right_cosets(),
309 };
310 match cong.check_state() {
311 Ok(_) => panic!(),
312 Err(_) => {}
313 }
314 }
315 Some(nsg) => {
316 let cong = nsg.cosets();
317 match cong.check_state() {
318 Ok(_) => {}
319 Err(_) => panic!(),
320 }
321 }
322 }
323 }
324 }
325}