algebraeon_groups/composition_table/
partition.rs

1use std::collections::{BTreeSet, HashSet};
2
3use super::group::*;
4use super::subset::*;
5
6#[derive(Clone, Debug)]
7pub struct PartitionState {
8    classes: Vec<BTreeSet<usize>>, //vector of conjugacy classes
9    lookup: Vec<usize>,            //for each element, the index of its conjugacy class
10}
11
12impl PartitionState {
13    pub fn check_state(&self, group: &Group) -> Result<(), &'static str> {
14        //is a partition
15        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        //lookup is correct
32        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    // is_left_cosets: Option<bool>,
59    // is_right_cosets: Option<bool>,
60}
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        //check equality by comparing lookup lists
73        //equal iff lookups are equal up to permutation
74        //build up a permutation f from indicies of self.lookup to indicies of other.lookup
75        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        //elements too big
229        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        //not a covering set
242        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        //not disjoint
255        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        //lookup values too big
268        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        //incorrect lookup values
281        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}