algebraeon_groups/composition_table/
partition.rs1use algebraeon_sets::combinatorics::Partition;
2
3use super::group::FiniteGroupMultiplicationTable;
4use super::subset::Subset;
5use std::collections::HashSet;
6
7#[derive(Debug)]
8pub struct GroupPartition<'a> {
9 pub group: &'a FiniteGroupMultiplicationTable,
10 pub partition: Partition,
11 }
14
15impl<'a> PartialEq for GroupPartition<'a> {
16 fn eq(&self, other: &Self) -> bool {
17 let grp = self.group;
18 if !std::ptr::eq(grp, other.group) {
19 return false;
20 }
21 let n = self.size();
22 if n != other.size() {
23 return false;
24 }
25 let mut f: Vec<Option<usize>> = vec![None; n];
29 #[allow(clippy::indexing_slicing)]
30 for i in 0..grp.size() {
31 match f[self.partition.project(i)] {
32 Some(expected_other_lookup_i) => {
33 if expected_other_lookup_i != other.partition.project(i) {
34 return false;
35 }
36 }
37 None => {
38 f[self.partition.project(i)] = Some(other.partition.project(i));
39 }
40 }
41 }
42 true
43 }
44}
45
46impl<'a> Eq for GroupPartition<'a> {}
47
48impl<'a> GroupPartition<'a> {
49 pub fn check_state(&self) -> Result<(), &'static str> {
50 if self.partition.num_elements() != self.group.size() {
51 return Err("Partition is of set of the wrong size");
52 }
53 Ok(())
54 }
55
56 pub fn size(&self) -> usize {
57 self.partition.size()
58 }
59
60 pub fn is_left_cosets(&self) -> bool {
61 let ident_class = Subset::new_unchecked(
62 self.group,
63 self.partition.class_containing(self.group.ident()).clone(),
64 );
65 match ident_class.to_subgroup() {
66 Some(ident_subgroup) => &ident_subgroup.left_cosets() == self,
67 None => false,
68 }
69 }
70
71 pub fn is_right_cosets(&self) -> bool {
72 let ident_class = Subset::new_unchecked(
73 self.group,
74 self.partition.class_containing(self.group.ident()).clone(),
75 );
76 match ident_class.to_subgroup() {
77 Some(ident_subgroup) => &ident_subgroup.right_cosets() == self,
78 None => false,
79 }
80 }
81
82 pub fn is_congruence(&self) -> bool {
83 self.is_left_cosets() && self.is_right_cosets()
84 }
85
86 pub fn from_subsets(
87 group: &'a FiniteGroupMultiplicationTable,
88 subsets: Vec<HashSet<usize>>,
89 ) -> Result<Self, ()> {
90 let classes = subsets;
91 let mut lookup = vec![0; group.size()];
92 for (class_idx, class) in classes.iter().enumerate() {
93 for x in class {
94 if *x >= group.size() {
95 return Err(());
96 }
97 lookup[*x] = class_idx;
98 }
99 }
100 let partition = Self {
101 group,
102 partition: Partition::new_unchecked(classes, lookup),
103 };
104 match partition.check_state() {
105 Ok(()) => {}
106 Err(_) => {
107 return Err(());
108 }
109 }
110 Ok(partition)
111 }
112}
113
114pub struct Congruence<'a> {
115 pub partition: GroupPartition<'a>,
116}
117
118impl<'a> Congruence<'a> {
119 pub fn check_state(&self) -> Result<(), &'static str> {
120 match self.partition.check_state() {
121 Ok(()) => {}
122 Err(msg) => {
123 return Err(msg);
124 }
125 }
126
127 if !self.partition.is_congruence() {
128 return Err("congruence is not a congruence");
129 }
130
131 Ok(())
132 }
133
134 pub fn size(&self) -> usize {
135 self.partition.size()
136 }
137
138 pub fn quotient_group(&self) -> FiniteGroupMultiplicationTable {
139 let n = self.size();
140 self.partition.group.check_state().unwrap();
141 FiniteGroupMultiplicationTable::new_unchecked(
142 n,
143 self.partition
144 .partition
145 .project(self.partition.group.ident()),
146 {
147 let mut inv = vec![0; n];
148 for i in 0..n {
149 inv[i] = self.partition.partition.project(
150 self.partition
151 .group
152 .inv(*self.partition.partition.get_class(i).iter().next().unwrap()),
153 );
154 }
155 inv
156 },
157 {
158 let mut mul = vec![vec![0; n]; n];
159 for i in 0..n {
160 for j in 0..n {
161 mul[i][j] = self.partition.partition.project(self.partition.group.mul(
162 *self.partition.partition.get_class(i).iter().next().unwrap(),
163 *self.partition.partition.get_class(j).iter().next().unwrap(),
164 ));
165 }
166 }
167 mul
168 },
169 None,
170 None,
171 )
172 }
173}
174
175#[cfg(test)]
176mod partition_tests {
177 use super::*;
178 use crate::composition_table::group::examples;
179
180 #[test]
181 fn congruence_check_state() {
182 let grp = examples::symmetric_group_structure(4);
183 for (sg, _gens) in grp.subgroups() {
184 match sg.to_normal_subgroup() {
185 None => {
186 let cong: Congruence<'_> = Congruence {
187 partition: sg.left_cosets(),
188 };
189 if let Ok(()) = cong.check_state() {
190 panic!();
191 }
192 let cong: Congruence<'_> = Congruence {
193 partition: sg.right_cosets(),
194 };
195 if let Ok(()) = cong.check_state() {
196 panic!();
197 }
198 }
199 Some(nsg) => {
200 let cong = nsg.cosets();
201 if cong.check_state().is_err() {
202 panic!();
203 }
204 }
205 }
206 }
207 }
208}