algebraeon_groups/composition_table/
homomorphism.rs

1use std::borrow::Borrow;
2use std::collections::BTreeSet;
3use std::collections::HashMap;
4
5use super::generating_set::GeneratingSet;
6use super::group::FiniteGroupMultiplicationTable;
7
8#[derive(Clone)]
9pub struct Homomorphism<
10    DomainT: Borrow<FiniteGroupMultiplicationTable> + Clone,
11    RangeT: Borrow<FiniteGroupMultiplicationTable> + Clone,
12> {
13    domain: DomainT,
14    range: RangeT,
15    func: Vec<usize>, //func : domain -> range    y = func[x]
16}
17
18impl<
19    DomainT: Borrow<FiniteGroupMultiplicationTable> + Clone,
20    RangeT: Borrow<FiniteGroupMultiplicationTable> + Clone,
21> Homomorphism<DomainT, RangeT>
22{
23    pub fn check_state(&self) -> Result<(), &'static str> {
24        //is function
25        if self.func.len() != self.domain.borrow().size() {
26            return Err("func size does not match domain size");
27        }
28
29        for x in self.domain.borrow().elems() {
30            if self.func[x] >= self.range.borrow().size() {
31                return Err("func image is too big for an element of the range");
32            }
33        }
34
35        //is homomorphism
36        for x in self.domain.borrow().elems() {
37            for y in self.domain.borrow().elems() {
38                if self.func[self.domain.borrow().mul(x, y)]
39                    != self.range.borrow().mul(self.func[x], self.func[y])
40                {
41                    return Err("homomorphism does not respect composition");
42                }
43            }
44        }
45
46        Ok(())
47    }
48
49    pub fn new_unchecked(domain: DomainT, range: RangeT, func: Vec<usize>) -> Self {
50        Self {
51            domain,
52            range,
53            func,
54        }
55    }
56
57    pub fn to_isomorphism(self) -> Option<Isomorphism<DomainT, RangeT>> {
58        let n = self.domain.borrow().size();
59        if n != self.range.borrow().size() {
60            return None;
61        }
62
63        let mut inv = vec![None; n];
64        #[allow(clippy::indexing_slicing)]
65        for x in 0..n {
66            match inv[self.func[x]] {
67                Some(_y) => {
68                    //self.func is not injective
69                    return None;
70                }
71                None => inv[self.func[x]] = Some(x),
72            }
73        }
74
75        let inv = inv
76            .into_iter()
77            .map(|x| if let Some(x_val) = x { x_val } else { panic!() })
78            .collect::<Vec<usize>>();
79
80        Some(Isomorphism {
81            left_group: self.domain,
82            right_group: self.range,
83            left_func: self.func.clone(),
84            right_func: inv,
85        })
86    }
87}
88
89#[derive(Clone)]
90pub struct Isomorphism<
91    LeftGrpT: Borrow<FiniteGroupMultiplicationTable> + Clone,
92    RightGrpT: Borrow<FiniteGroupMultiplicationTable> + Clone,
93> {
94    left_group: LeftGrpT,
95    right_group: RightGrpT,
96    left_func: Vec<usize>,
97    right_func: Vec<usize>,
98}
99
100impl<
101    LeftGrpT: Borrow<FiniteGroupMultiplicationTable> + Clone,
102    RightGrpT: Borrow<FiniteGroupMultiplicationTable> + Clone,
103> Isomorphism<LeftGrpT, RightGrpT>
104{
105    pub fn check_state(&self) -> Result<(), &'static str> {
106        let left_hom = Homomorphism {
107            domain: self.left_group.borrow(),
108            range: self.right_group.borrow(),
109            func: self.left_func.clone(),
110        };
111        match left_hom.check_state() {
112            Ok(()) => {}
113            Err(msg) => {
114                return Err(msg);
115            }
116        }
117
118        let right_hom = Homomorphism {
119            domain: self.right_group.borrow(),
120            range: self.left_group.borrow(),
121            func: self.right_func.clone(),
122        };
123        match right_hom.check_state() {
124            Ok(()) => {}
125            Err(msg) => {
126                return Err(msg);
127            }
128        }
129
130        if self.left_group.borrow().size() != self.right_group.borrow().size() {
131            return Err("isomorphism group sizes dont match");
132        }
133
134        //are mutually inverse
135        //only need to check one of left/right inverse because injective/surjective individually imply bijective once the sizes are the same
136        for x in self.left_group.borrow().elems() {
137            if x != self.right_func[self.left_func[x]] {
138                return Err("isomorphism not inv");
139            }
140        }
141
142        Ok(())
143    }
144}
145
146//return an isomorphism from domain to range if one exists
147//return None if no isomorphism exists
148#[allow(clippy::too_many_lines)]
149pub fn find_isomorphism<'a, 'b>(
150    domain: &'a FiniteGroupMultiplicationTable,
151    range: &'b FiniteGroupMultiplicationTable,
152) -> Option<Isomorphism<&'a FiniteGroupMultiplicationTable, &'b FiniteGroupMultiplicationTable>> {
153    let group_size = domain.size();
154    if group_size != range.size() {
155        return None;
156    }
157
158    //an element profile can be generated for each element in the range and domain
159    //an isomorphism can only every map elements of the same profile to each other
160    //use this is restrict the number of things to check
161    #[allow(clippy::items_after_statements)]
162    #[derive(Debug, PartialEq, Eq, Hash)]
163    struct ElementProfile {
164        order: usize,
165        mul_order: BTreeSet<usize>, //helped a lot when comparing non-isomorphic groups of order 144
166    }
167
168    impl ElementProfile {
169        fn new(x: usize, group: &FiniteGroupMultiplicationTable) -> Self {
170            debug_assert!(x < group.size());
171            ElementProfile {
172                order: group.order(x).unwrap(),
173                mul_order: group
174                    .elems()
175                    .map(|y| group.order(group.mul(x, y)).unwrap())
176                    .collect(),
177            }
178        }
179    }
180
181    //want to quickly lookup [element -> profile] on the domain side
182    let domain_elem_profiles: Vec<ElementProfile> = domain
183        .elems()
184        .map(|x| ElementProfile::new(x, domain))
185        .collect();
186
187    // println!("domain_elem_profiles {:?}", domain_elem_profiles);
188
189    //want to quickly lookup [profile -> elements] on the range side
190    let mut range_elem_profiles: HashMap<ElementProfile, Vec<usize>> = HashMap::new();
191    for x in range.elems() {
192        let p = ElementProfile::new(x, range);
193        match range_elem_profiles.get_mut(&p) {
194            Some(elems) => {
195                elems.push(x);
196            }
197            None => {
198                range_elem_profiles.insert(p, vec![x]);
199            }
200        }
201    }
202
203    // println!("range_elem_profiles {:?}", range_elem_profiles);
204
205    struct GenInfo<'c> {
206        gen_list: Vec<usize>,
207        gen_set: GeneratingSet<'c>,
208        image_options: Vec<Vec<usize>>,
209        quant_to_check: usize,
210    }
211
212    fn find_new_gen_info<'c>(
213        domain: &'c FiniteGroupMultiplicationTable,
214        domain_elem_profiles: &[ElementProfile],
215        range_elem_profiles: &HashMap<ElementProfile, Vec<usize>>,
216    ) -> Result<GenInfo<'c>, ()> {
217        let domain_gen_set = domain.generating_set();
218        let domain_gens = domain_gen_set.gens();
219
220        //TODO: compute a smaller subset of possible image points
221        //e.g. each gen can only map to something of the same order
222        let mut gen_image_options: Vec<Vec<usize>> = vec![];
223        for g in domain_gens {
224            match range_elem_profiles.get(&domain_elem_profiles[*g]) {
225                Some(r_elems) => gen_image_options.push(r_elems.clone()),
226                None => {
227                    return Err(()); //there is no isomorphism
228                }
229            }
230        }
231
232        let mut num_to_check: usize = 1;
233        for gen_image_option in &gen_image_options {
234            num_to_check = match num_to_check.checked_mul(gen_image_option.len()) {
235                Some(prod) => prod,
236                None => usize::MAX,
237            };
238        }
239
240        Ok(GenInfo {
241            gen_list: domain_gens.clone(),
242            gen_set: domain_gen_set,
243            image_options: gen_image_options,
244            quant_to_check: num_to_check,
245        })
246    }
247
248    let mut current_gen_info;
249    match find_new_gen_info(domain, &domain_elem_profiles, &range_elem_profiles) {
250        Ok(gen_info) => current_gen_info = gen_info,
251        Err(()) => {
252            return None;
253        }
254    }
255
256    // println!("group size {}", group_size);
257    // println!("generating set {}", current_gen_info.quant_to_check);
258
259    'outer_loop: loop {
260        //loop over all possible images of the domain generators
261        let mut image_option_counter = vec![0; current_gen_info.gen_list.len()];
262        let mut already_checked: usize = 0;
263        'loop_label: loop {
264            //try to find a better generating set every few loops
265            //better meaning there are fewer possible images to check than we currently have left to go
266            if already_checked % 128 == 127 {
267                // println!("maybe better generating set {}", already_checked);
268                match find_new_gen_info(domain, &domain_elem_profiles, &range_elem_profiles) {
269                    Ok(gen_info) => {
270                        if gen_info.quant_to_check
271                            < current_gen_info.quant_to_check - already_checked
272                        {
273                            // println!("better generating set {} {}", gen_info.quant_to_check, gen_info.gen_list.len());
274                            current_gen_info = gen_info;
275                            continue 'outer_loop;
276                        }
277                    }
278                    Err(()) => {
279                        return None;
280                    }
281                }
282            }
283
284            //compute homomorphism sending domain_gens -> image_gens
285            if let Some(f) = current_gen_info
286                .gen_set
287                .generated_homomorphism(
288                    &image_option_counter
289                        .iter()
290                        .enumerate()
291                        .map(|(i, j)| current_gen_info.image_options[i][*j])
292                        .collect(),
293                    range,
294                )
295                .unwrap()
296            {
297                if let Some(f_iso) = f.to_isomorphism() {
298                    return Some(f_iso);
299                }
300            }
301            already_checked += 1;
302
303            //compute next gen_img
304            'for_label: {
305                for i in 0..current_gen_info.gen_list.len() {
306                    image_option_counter[i] += 1;
307                    if image_option_counter[i] == current_gen_info.image_options[i].len() {
308                        image_option_counter[i] = 0;
309                    } else {
310                        break 'for_label;
311                    }
312                }
313                break 'loop_label;
314            }
315        }
316        return None;
317    }
318}
319
320#[cfg(test)]
321mod homomorphism_tests {
322    use crate::{
323        composition_table::group::examples,
324        free_group::todd_coxeter::FinitelyGeneratedGroupPresentation,
325    };
326
327    use super::*;
328
329    #[test]
330    fn homomorphism_state() {
331        {
332            //identity map
333            let grp_g = examples::cyclic_group_structure(6);
334            let grp_h = examples::cyclic_group_structure(6);
335            let f = Homomorphism {
336                domain: &grp_g,
337                range: &grp_h,
338                func: vec![0, 1, 2, 3, 4, 5],
339            };
340            if f.check_state().is_err() {
341                panic!()
342            }
343        }
344
345        {
346            //injective map
347            let grp_g = examples::cyclic_group_structure(3);
348            let grp_h = examples::cyclic_group_structure(6);
349            let f = Homomorphism {
350                domain: &grp_g,
351                range: &grp_h,
352                func: vec![0, 2, 4],
353            };
354            if f.check_state().is_err() {
355                panic!()
356            }
357        }
358
359        {
360            //surjective map
361            let grp_g = examples::cyclic_group_structure(6);
362            let grp_h = examples::cyclic_group_structure(3);
363            let f = Homomorphism {
364                domain: &grp_g,
365                range: &grp_h,
366                func: vec![0, 1, 2, 0, 1, 2],
367            };
368            if f.check_state().is_err() {
369                panic!()
370            }
371        }
372
373        {
374            //bad func
375            let grp_g = examples::cyclic_group_structure(3);
376            let grp_h = examples::cyclic_group_structure(6);
377            let f = Homomorphism {
378                domain: &grp_g,
379                range: &grp_h,
380                func: vec![0, 1, 2, 3],
381            };
382            if let Ok(()) = f.check_state() {
383                panic!()
384            }
385        }
386
387        {
388            //bad func
389            let grp_g = examples::cyclic_group_structure(6);
390            let grp_h = examples::cyclic_group_structure(3);
391            let f = Homomorphism {
392                domain: &grp_g,
393                range: &grp_h,
394                func: vec![0, 1, 2, 3, 4, 5, 6],
395            };
396            if let Ok(()) = f.check_state() {
397                panic!()
398            }
399        }
400
401        {
402            //bad hom
403            let grp_g = examples::cyclic_group_structure(3);
404            let grp_h = examples::cyclic_group_structure(6);
405            let f = Homomorphism {
406                domain: &grp_g,
407                range: &grp_h,
408                func: vec![0, 1, 2],
409            };
410            if let Ok(()) = f.check_state() {
411                panic!()
412            }
413        }
414    }
415
416    #[test]
417    fn isomorphism_state() {
418        {
419            //happy
420            let grp_g = examples::cyclic_group_structure(6);
421            let grp_h = examples::cyclic_group_structure(6);
422            let f = Isomorphism {
423                left_group: &grp_g,
424                right_group: &grp_h,
425                left_func: vec![0, 5, 4, 3, 2, 1],
426                right_func: vec![0, 5, 4, 3, 2, 1],
427            };
428            if f.check_state().is_err() {
429                panic!()
430            }
431        }
432
433        {
434            //size mismatch
435            let grp_g = examples::cyclic_group_structure(3);
436            let grp_h = examples::cyclic_group_structure(6);
437            let f = Isomorphism {
438                left_group: &grp_g,
439                right_group: &grp_h,
440                left_func: vec![0, 2, 4],
441                right_func: vec![0, 1, 2, 0, 1, 2],
442            };
443            if let Ok(()) = f.check_state() {
444                panic!()
445            }
446        }
447
448        {
449            //not inv
450            let grp_g = examples::cyclic_group_structure(6);
451            let grp_h = examples::cyclic_group_structure(6);
452            let f = Isomorphism {
453                left_group: &grp_g,
454                right_group: &grp_h,
455                left_func: vec![0, 2, 4, 0, 2, 4],
456                right_func: vec![0, 5, 4, 3, 2, 1],
457            };
458            if let Ok(()) = f.check_state() {
459                panic!()
460            }
461        }
462    }
463
464    #[test]
465    fn homomorphism_to_isomorphism() {
466        {
467            //happy
468            let grp_g = examples::cyclic_group_structure(7);
469            let grp_h = examples::cyclic_group_structure(7);
470            let f = Homomorphism {
471                domain: &grp_g,
472                range: &grp_h,
473                func: vec![0, 3, 6, 2, 5, 1, 4],
474            };
475            f.check_state().unwrap();
476            if let Some(_f_iso) = f.to_isomorphism() {
477            } else {
478                panic!()
479            }
480        }
481
482        {
483            //size mismatch
484            let grp_g = examples::cyclic_group_structure(3);
485            let grp_h = examples::cyclic_group_structure(6);
486            let f = Homomorphism {
487                domain: &grp_g,
488                range: &grp_h,
489                func: vec![0, 2, 4],
490            };
491            f.check_state().unwrap();
492            if let Some(_f_iso) = f.to_isomorphism() {
493                panic!()
494            }
495        }
496
497        {
498            //not surjective
499            let grp_g = examples::cyclic_group_structure(6);
500            let grp_h = examples::cyclic_group_structure(6);
501            let f = Homomorphism {
502                domain: &grp_g,
503                range: &grp_h,
504                func: vec![0, 2, 4, 0, 2, 4],
505            };
506            f.check_state().unwrap();
507            if let Some(__iso) = f.to_isomorphism() {
508                panic!()
509            }
510        }
511    }
512
513    #[test]
514    fn test_find_isomorphism() {
515        {
516            let grp_g = examples::symmetric_group_structure(3);
517            let grp_h = examples::dihedral_group_structure(3);
518
519            if let Some(f) = find_isomorphism(&grp_g, &grp_h) {
520                assert!(std::ptr::eq(&grp_g, f.left_group));
521                assert!(std::ptr::eq(&grp_h, f.right_group));
522            } else {
523                panic!()
524            }
525        }
526
527        {
528            let grp_g = examples::symmetric_group_structure(3);
529            let grp_h = examples::cyclic_group_structure(6);
530
531            if let Some(_f) = find_isomorphism(&grp_g, &grp_h) {
532                panic!();
533            }
534        }
535
536        {
537            let grp_g = examples::symmetric_group_structure(5);
538            let mut grp_h = FinitelyGeneratedGroupPresentation::new();
539            let a = grp_h.add_generator();
540            let b = grp_h.add_generator();
541            let c = grp_h.add_generator();
542            grp_h.add_relation(a.pow(2));
543            grp_h.add_relation(b.pow(2));
544            grp_h.add_relation(c.pow(2));
545            grp_h.add_relation((&a * &c).pow(2));
546            grp_h.add_relation((&a * &b).pow(3));
547            grp_h.add_relation((&b * &c).pow(5));
548            let grp_h = grp_h.into_finite_group(); //A5 x C2
549
550            if let Some(_f) = find_isomorphism(&grp_g, &grp_h) {
551                panic!()
552            }
553        }
554    }
555}