algebraeon_groups/composition_table/
subgroup.rs1use std::collections::BTreeSet;
2use std::collections::HashSet;
3
4use algebraeon_sets::combinatorics::Partition;
5
6use super::group::FiniteGroupMultiplicationTable;
7use super::normal_subgroup::NormalSubgroup;
8use super::partition::GroupPartition;
9use super::subset::Subset;
10use std::hash::Hash;
11
12#[derive(Clone)]
13pub struct Subgroup<'a> {
14 pub subset: Subset<'a>,
15}
16
17impl<'a> PartialEq for Subgroup<'a> {
18 fn eq(&self, other: &Self) -> bool {
19 self.subset == other.subset
20 }
21}
22
23impl<'a> Eq for Subgroup<'a> {}
24
25impl<'a> Hash for Subgroup<'a> {
26 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
27 self.subset
28 .elems()
29 .iter()
30 .collect::<BTreeSet<_>>()
31 .hash(state);
32 }
33}
34
35impl<'a> Subgroup<'a> {
36 pub fn check_state(&self) -> Result<(), &'static str> {
37 match self.subset.check_state() {
38 Ok(()) => {}
39 Err(msg) => {
40 return Err(msg);
41 }
42 }
43
44 if !self.subset.is_subgroup() {
45 return Err("subgroup is not closed under composition");
46 }
47
48 Ok(())
49 }
50
51 pub fn size(&self) -> usize {
52 self.subset.size()
53 }
54
55 pub fn to_group(&self) -> FiniteGroupMultiplicationTable {
56 let sg_elems: Vec<usize> = self.subset.elems().clone().into_iter().collect();
57 let k = sg_elems.len();
58 let mut group_to_subgroup: Vec<Option<usize>> = vec![None; self.subset.group().size()];
59 for (i, x) in sg_elems.iter().enumerate() {
60 group_to_subgroup[*x] = Some(i);
61 }
62 FiniteGroupMultiplicationTable::new_unchecked(
64 self.size(),
65 group_to_subgroup[self.subset.group().ident()].unwrap(),
66 (0..k)
67 .map(|x| group_to_subgroup[self.subset.group().inv(sg_elems[x])].unwrap())
68 .collect(),
69 (0..k)
70 .map(|x| {
71 (0..k)
72 .map(|y| {
73 group_to_subgroup[self.subset.group().mul(sg_elems[x], sg_elems[y])]
74 .unwrap()
75 })
76 .collect()
77 })
78 .collect(),
79 None,
80 None,
81 )
82 }
83
84 pub fn left_cosets(&'_ self) -> GroupPartition<'_> {
85 let mut cosets: Vec<HashSet<usize>> = vec![];
86 let mut coset_lookup = vec![0; self.subset.group().size()];
87 let mut missing: HashSet<usize> = self.subset.group().elems().collect();
88 while !missing.is_empty() {
89 let g = missing.iter().next().unwrap();
90 let coset = self.subset.left_mul(*g).unwrap().elems().clone();
91 for h in &coset {
92 missing.remove(h);
93 coset_lookup[*h] = cosets.len();
94 }
95 cosets.push(coset);
96 }
97 GroupPartition {
98 group: self.subset.group(),
99 partition: Partition::new_unchecked(cosets, coset_lookup),
100 }
101 }
102
103 pub fn right_cosets(&'_ self) -> GroupPartition<'_> {
104 let mut cosets: Vec<HashSet<usize>> = vec![];
105 let mut coset_lookup = vec![0; self.subset.group().size()];
106 let mut missing: HashSet<usize> = self.subset.group().elems().collect();
107 while !missing.is_empty() {
108 let g = missing.iter().next().unwrap();
109 let coset = self.subset.right_mul(*g).unwrap().elems().clone();
110 for h in &coset {
111 missing.remove(h);
112 coset_lookup[*h] = cosets.len();
113 }
114 cosets.push(coset);
115 }
116 GroupPartition {
117 group: self.subset.group(),
118 partition: Partition::new_unchecked(cosets, coset_lookup),
119 }
120 }
121
122 pub fn is_normal_subgroup(&self) -> bool {
123 for x in self.subset.elems() {
124 for g in self.subset.group().elems() {
125 if !self.subset.elems().contains(
126 &self
127 .subset
128 .group()
129 .mul(self.subset.group().mul(g, *x), self.subset.group().inv(g)),
130 )
131 {
133 return false;
134 }
135 }
136 }
137 true
138 }
139
140 pub fn to_normal_subgroup(&'_ self) -> Option<NormalSubgroup<'_>> {
141 if self.is_normal_subgroup() {
142 Some(NormalSubgroup::new_unchecked(self.clone()))
143 } else {
144 None
145 }
146 }
147}
148
149#[cfg(test)]
150mod subgroup_tests {
151 use super::*;
152 use crate::composition_table::group::examples;
153
154 #[test]
155 fn subgroup_counts() {
156 for (grp, num_sgs) in vec![
157 (examples::cyclic_group_structure(12), 6),
158 (examples::cyclic_group_structure(13), 2),
159 (examples::dihedral_group_structure(1), 2),
160 (examples::dihedral_group_structure(12), 34),
161 (examples::dihedral_group_structure(13), 16),
162 (examples::symmetric_group_structure(1), 1),
163 (examples::symmetric_group_structure(2), 2),
164 (examples::symmetric_group_structure(3), 6),
165 (examples::symmetric_group_structure(4), 30),
166 (examples::symmetric_group_structure(5), 156),
167 ] {
168 assert_eq!(grp.subgroups().len(), num_sgs);
169 }
170 }
171
172 #[test]
173 fn subgroup_state() {
174 for grp in [
176 examples::symmetric_group_structure(4),
177 examples::dihedral_group_structure(12),
178 examples::cyclic_group_structure(8),
179 ] {
180 let sgs = grp.subgroups();
181 for (sg, gens) in sgs {
182 sg.check_state().unwrap();
184 gens.check_state().unwrap();
185 }
188 }
189 }
190
191 #[test]
192 fn subgroup_left_cosets() {
193 use crate::examples::symmetric::Permutation;
194
195 let (grp, _perms, elems) = Permutation::<3>::symmetric_composition_table();
196 let sg = Subgroup {
197 subset: Subset::new_unchecked(
198 &grp,
199 vec![
200 elems[&Permutation::new([0, 1, 2]).unwrap()],
201 elems[&Permutation::new([1, 0, 2]).unwrap()],
202 ]
203 .into_iter()
204 .collect(),
205 ),
206 };
207 sg.check_state().unwrap();
208 let cosets = sg.left_cosets();
209 cosets.check_state().unwrap();
210 assert_eq!(cosets.size(), 3);
211 assert!(
212 cosets
213 == GroupPartition::from_subsets(
214 &grp,
215 vec![
216 vec![
217 elems[&Permutation::new([0, 1, 2]).unwrap()],
218 elems[&Permutation::new([1, 0, 2]).unwrap()],
219 ]
220 .into_iter()
221 .collect(),
222 vec![
223 elems[&Permutation::new([1, 2, 0]).unwrap()],
224 elems[&Permutation::new([2, 1, 0]).unwrap()],
225 ]
226 .into_iter()
227 .collect(),
228 vec![
229 elems[&Permutation::new([2, 0, 1]).unwrap()],
230 elems[&Permutation::new([0, 2, 1]).unwrap()],
231 ]
232 .into_iter()
233 .collect()
234 ]
235 )
236 .unwrap()
237 );
238 }
239
240 #[test]
241 fn subgroup_right_cosets() {
242 use crate::examples::symmetric::Permutation;
243
244 let (grp, _perms, elems) = Permutation::<3>::symmetric_composition_table();
245 let sg = Subgroup {
246 subset: Subset::new_unchecked(
247 &grp,
248 vec![
249 elems[&Permutation::new([0, 1, 2]).unwrap()],
250 elems[&Permutation::new([1, 0, 2]).unwrap()],
251 ]
252 .into_iter()
253 .collect(),
254 ),
255 };
256 sg.check_state().unwrap();
257 let cosets = sg.right_cosets();
258 println!("{:#?}", cosets);
259 cosets.check_state().unwrap();
260 assert_eq!(cosets.size(), 3);
261 assert!(
262 cosets
263 == GroupPartition::from_subsets(
264 &grp,
265 vec![
266 vec![
267 elems[&Permutation::new([0, 1, 2]).unwrap()],
268 elems[&Permutation::new([1, 0, 2]).unwrap()],
269 ]
270 .into_iter()
271 .collect(),
272 vec![
273 elems[&Permutation::new([1, 2, 0]).unwrap()],
274 elems[&Permutation::new([0, 2, 1]).unwrap()],
275 ]
276 .into_iter()
277 .collect(),
278 vec![
279 elems[&Permutation::new([2, 0, 1]).unwrap()],
280 elems[&Permutation::new([2, 1, 0]).unwrap()],
281 ]
282 .into_iter()
283 .collect()
284 ]
285 )
286 .unwrap()
287 );
288 }
289}