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                && let Some(f_iso) = f.to_isomorphism()
297            {
298                return Some(f_iso);
299            }
300            already_checked += 1;
301
302            //compute next gen_img
303            'for_label: {
304                for i in 0..current_gen_info.gen_list.len() {
305                    image_option_counter[i] += 1;
306                    if image_option_counter[i] == current_gen_info.image_options[i].len() {
307                        image_option_counter[i] = 0;
308                    } else {
309                        break 'for_label;
310                    }
311                }
312                break 'loop_label;
313            }
314        }
315        return None;
316    }
317}
318
319#[cfg(test)]
320mod homomorphism_tests {
321    use crate::{
322        composition_table::group::examples,
323        free_group::todd_coxeter::FinitelyGeneratedGroupPresentation,
324    };
325
326    use super::*;
327
328    #[test]
329    fn homomorphism_state() {
330        {
331            //identity map
332            let grp_g = examples::cyclic_group_structure(6);
333            let grp_h = examples::cyclic_group_structure(6);
334            let f = Homomorphism {
335                domain: &grp_g,
336                range: &grp_h,
337                func: vec![0, 1, 2, 3, 4, 5],
338            };
339            if f.check_state().is_err() {
340                panic!()
341            }
342        }
343
344        {
345            //injective map
346            let grp_g = examples::cyclic_group_structure(3);
347            let grp_h = examples::cyclic_group_structure(6);
348            let f = Homomorphism {
349                domain: &grp_g,
350                range: &grp_h,
351                func: vec![0, 2, 4],
352            };
353            if f.check_state().is_err() {
354                panic!()
355            }
356        }
357
358        {
359            //surjective map
360            let grp_g = examples::cyclic_group_structure(6);
361            let grp_h = examples::cyclic_group_structure(3);
362            let f = Homomorphism {
363                domain: &grp_g,
364                range: &grp_h,
365                func: vec![0, 1, 2, 0, 1, 2],
366            };
367            if f.check_state().is_err() {
368                panic!()
369            }
370        }
371
372        {
373            //bad func
374            let grp_g = examples::cyclic_group_structure(3);
375            let grp_h = examples::cyclic_group_structure(6);
376            let f = Homomorphism {
377                domain: &grp_g,
378                range: &grp_h,
379                func: vec![0, 1, 2, 3],
380            };
381            if let Ok(()) = f.check_state() {
382                panic!()
383            }
384        }
385
386        {
387            //bad func
388            let grp_g = examples::cyclic_group_structure(6);
389            let grp_h = examples::cyclic_group_structure(3);
390            let f = Homomorphism {
391                domain: &grp_g,
392                range: &grp_h,
393                func: vec![0, 1, 2, 3, 4, 5, 6],
394            };
395            if let Ok(()) = f.check_state() {
396                panic!()
397            }
398        }
399
400        {
401            //bad hom
402            let grp_g = examples::cyclic_group_structure(3);
403            let grp_h = examples::cyclic_group_structure(6);
404            let f = Homomorphism {
405                domain: &grp_g,
406                range: &grp_h,
407                func: vec![0, 1, 2],
408            };
409            if let Ok(()) = f.check_state() {
410                panic!()
411            }
412        }
413    }
414
415    #[test]
416    fn isomorphism_state() {
417        {
418            //happy
419            let grp_g = examples::cyclic_group_structure(6);
420            let grp_h = examples::cyclic_group_structure(6);
421            let f = Isomorphism {
422                left_group: &grp_g,
423                right_group: &grp_h,
424                left_func: vec![0, 5, 4, 3, 2, 1],
425                right_func: vec![0, 5, 4, 3, 2, 1],
426            };
427            if f.check_state().is_err() {
428                panic!()
429            }
430        }
431
432        {
433            //size mismatch
434            let grp_g = examples::cyclic_group_structure(3);
435            let grp_h = examples::cyclic_group_structure(6);
436            let f = Isomorphism {
437                left_group: &grp_g,
438                right_group: &grp_h,
439                left_func: vec![0, 2, 4],
440                right_func: vec![0, 1, 2, 0, 1, 2],
441            };
442            if let Ok(()) = f.check_state() {
443                panic!()
444            }
445        }
446
447        {
448            //not inv
449            let grp_g = examples::cyclic_group_structure(6);
450            let grp_h = examples::cyclic_group_structure(6);
451            let f = Isomorphism {
452                left_group: &grp_g,
453                right_group: &grp_h,
454                left_func: vec![0, 2, 4, 0, 2, 4],
455                right_func: vec![0, 5, 4, 3, 2, 1],
456            };
457            if let Ok(()) = f.check_state() {
458                panic!()
459            }
460        }
461    }
462
463    #[test]
464    fn homomorphism_to_isomorphism() {
465        {
466            //happy
467            let grp_g = examples::cyclic_group_structure(7);
468            let grp_h = examples::cyclic_group_structure(7);
469            let f = Homomorphism {
470                domain: &grp_g,
471                range: &grp_h,
472                func: vec![0, 3, 6, 2, 5, 1, 4],
473            };
474            f.check_state().unwrap();
475            if let Some(_f_iso) = f.to_isomorphism() {
476            } else {
477                panic!()
478            }
479        }
480
481        {
482            //size mismatch
483            let grp_g = examples::cyclic_group_structure(3);
484            let grp_h = examples::cyclic_group_structure(6);
485            let f = Homomorphism {
486                domain: &grp_g,
487                range: &grp_h,
488                func: vec![0, 2, 4],
489            };
490            f.check_state().unwrap();
491            if let Some(_f_iso) = f.to_isomorphism() {
492                panic!()
493            }
494        }
495
496        {
497            //not surjective
498            let grp_g = examples::cyclic_group_structure(6);
499            let grp_h = examples::cyclic_group_structure(6);
500            let f = Homomorphism {
501                domain: &grp_g,
502                range: &grp_h,
503                func: vec![0, 2, 4, 0, 2, 4],
504            };
505            f.check_state().unwrap();
506            if let Some(__iso) = f.to_isomorphism() {
507                panic!()
508            }
509        }
510    }
511
512    #[test]
513    fn test_find_isomorphism() {
514        {
515            let grp_g = examples::symmetric_group_structure(3);
516            let grp_h = examples::dihedral_group_structure(3);
517
518            if let Some(f) = find_isomorphism(&grp_g, &grp_h) {
519                assert!(std::ptr::eq(&grp_g, f.left_group));
520                assert!(std::ptr::eq(&grp_h, f.right_group));
521            } else {
522                panic!()
523            }
524        }
525
526        {
527            let grp_g = examples::symmetric_group_structure(3);
528            let grp_h = examples::cyclic_group_structure(6);
529
530            if let Some(_f) = find_isomorphism(&grp_g, &grp_h) {
531                panic!();
532            }
533        }
534
535        {
536            let grp_g = examples::symmetric_group_structure(5);
537            let mut grp_h = FinitelyGeneratedGroupPresentation::new();
538            let a = grp_h.add_generator();
539            let b = grp_h.add_generator();
540            let c = grp_h.add_generator();
541            grp_h.add_relation(a.pow(2));
542            grp_h.add_relation(b.pow(2));
543            grp_h.add_relation(c.pow(2));
544            grp_h.add_relation((&a * &c).pow(2));
545            grp_h.add_relation((&a * &b).pow(3));
546            grp_h.add_relation((&b * &c).pow(5));
547            let grp_h = grp_h.into_finite_group(); //A5 x C2
548
549            if let Some(_f) = find_isomorphism(&grp_g, &grp_h) {
550                panic!()
551            }
552        }
553    }
554}