"""
Module implementing order search functions.
"""
import logging
import math
import sys
from functools import wraps
import FiSGO.PrimesHandler as ph
import FiSGO.SimpleGroups as sg
# TODO: Test all candidates functions
# TODO: Test absolute bounding works
# Setup logger to stdout
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
[docs]
def order_search_logger(func):
""" Decorator for logging candidate search functions. """
@wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"Starting candidate search for {func.__name__.replace("candidates_", "")} groups.")
result = func(*args, **kwargs)
logging.info("Found {0} compatible groups.".format(len(result)))
logging.info(f"Finished candidate search for {func.__name__.replace('candidates_', '')} groups.")
return result
return wrapper
[docs]
def add_lists(list1: list[int], list2: list[int]) -> list[int]:
"""
Adds two lists of integers of the same length pointwise.
Example:
>>> add_lists([1,2,3],[0,1,2])
[1, 3, 5]
:param list1: A list of integers.
:param list2: A list of integers.
:return: Pointwise addition of list1 and list2.
"""
try:
return [list1[i] + list2[i] for i in range(max(len(list1), len(list2)))]
except IndexError:
raise IndexError("The lists must have the same length")
[docs]
def substract_lists(list1: list[int], list2: list[int]) -> list[int]:
"""
Substracts two lists of integers with the same length pointwise.
Example:
>>> substract_lists([1,2,3],[0,1,2])
[1, 1, 1]
:param list1: A list of integers.
:param list2: A list of integers.
:return: Pointwise substraction of list1 and list2, list1-list2.
"""
try:
return [list1[i] - list2[i] for i in range(max(len(list1), len(list2)))]
except IndexError:
raise IndexError("The lists must have the same length")
[docs]
def prod_scanned(factored1: tuple[list[int], int], factored2: tuple[list[int], int]) -> tuple[list[int], int]:
"""
Given two tuples originating from PrimesHandler.prime_scanner with the same upper_bound (or same length), returns
the product of both decompositions.
Idea: if factored1 corresponds to a number n1 and factored2 to n2, then
prod_scanned returns Primes.prime_scanner(n1*n2, upper_bound) without having to construct n1*n2 explicitely.
Example:
>>> prod_scanned(([1,1],1), ([3,0], 7))
([4, 1], 7)
:param factored1: Tuple with a list of integers and an integer.
:param factored2: Tuple with a list of integers and an integer.
:return: Partially factorized product of the numbers represented by factored1 and factored2.
"""
return add_lists(factored1[0], factored2[0]), factored1[1]*factored2[1]
[docs]
def div_scanned(factored1: tuple[list[int], int], factored2: tuple[list[int], int]) -> tuple[list[int], int]:
"""
Given two tuples originating from PrimesHandler.prime_scanner with the same upper_bound, returns the division
of both decompositions.
Idea: if factored1 corresponds to a number n1 and factored2 to n2, then prod_scanned returns
Primes.prime_scanner(n1/n2, upper_bound) without having to construct n1/n2 explicitely.
.. caution:: May return negative factors if the n1/n2 is not an integer! We expect the input to already be correct.
Example:
>>> div_scanned(([3,1],49), ([1,1], 7))
([2, 0], 7)
:param factored1: Tuple with a list of integers and an integer.
:param factored2: Tuple with a list of integers and an integer.
:return: Partially factorized division of the numbers represented by factored1 and factored2.
"""
return substract_lists(factored1[0], factored2[0]), factored1[1]//factored2[1]
[docs]
def check_candidate(code: str, bound: list[int]) -> bool:
"""
Given a simple group code and a bound, determines if the group order divides the given bound.
:param code: A simple group code.
:param bound: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means
the order of the group must divide 2^2*5^1*11^3.
:return: True if the order of the group divides the given bound, otherwise, returns False.
"""
group_order_factors = ph.factor(sg.simple_group(code).order(), list_output=True)[0]
if len(group_order_factors) > len(bound):
return False
for i in range(len(group_order_factors)):
if group_order_factors[i] > bound[i]:
return False
return True
[docs]
def prime_bound_compatiblity(order: tuple[list[int], int], bound: list[int]) -> bool:
"""
Given the partial factorization of a number and a bound to the factorized part, the function checks
if the given factorization satisfies the bound.
A partial factorization refers to a number given in the format of PrimesHandler.prime_scanner.
.. caution:: the length of order[0] is expected to be the same as bound, we are checking one factorization against the
other
Example:
>>> prime_bound_compatiblity(([1,2],1), [3,3])
True
>>> prime_bound_compatiblity(([1,2],7), [3,3])
False
>>> prime_bound_compatiblity(([2,6],1), [3,3])
False
:param order: Tuple with a list of integers and an integer.
:param bound: List of integers.
:return: True if order is bounded by bound, False otherwise.
"""
if order[1] != 1:
# There have appeared primes which are not in prime_bounds, so this group is not a candidate
return False
# We now check prime order compatibility against the relative order
for j, b in enumerate(bound):
if order[0][j] > b:
# A factor is greater than allowed, so this group is not a candidate
return False
return True
[docs]
def absolute_bound_filter(group_list: list[str] | list[sg.SimpleGroup], bound: int, codes: bool):
"""
Given a list of simple groups, returns only those whose order is less than or equal to a given bound.
:param group_list: List of simple group codes (strings) or SimpleGroup derived objects corresponding to specific
simple groups.
:param bound: An integer.
:param codes: True if group_list provides simple group codes, False if it provides SimpleGroup derived objects.
:return: Returns a list of simple groups codes (strings) if codes is True, or a list of SimpleGroup derived objects
if codes is false. The list contains only simple groups whose order is less than or equal to bound
"""
if codes:
for code in group_list:
if sg.simple_group(code).order() > bound:
group_list.remove(code)
else:
for group in group_list:
if group.order() > bound:
group_list.remove(group)
return group_list
[docs]
def powers_sequence(prime: int, n: int) -> int:
"""
The function returns the exponent corresponding to the maximum power of 'prime' that divides prime*n.
Rather than calculating prime*n and computing PrimesHandler.contained_power(prime*n, prime), this is done recursively.
Example: If prime = 3, then the sequence is [1,1,2,1,1,2,1,1,3,1,...] where each entry corresponds to the power of 3
contained in [3,6,9,12,15,18,21,24,27,30,...].
.. caution:: We index the sequence starting at n=1.
:param prime: A prime number.
:param n: A positive integer.
:return: The n-th term of the described sequence.
"""
if n % prime != 0:
return 1
else:
return 1 + powers_sequence(prime, n // prime)
[docs]
def candidate_from_power(max_power: int, prime: int) -> int:
"""
Given a positive integer 'max_power' and a prime number 'prime', the function calculates which is the largest number
n such that prime^max_power divides n!. To do this, we consider the summation sequence s(m) of OrderSearch.powers_sequence,
whose m-th term precisely gives the maximum power of 'prime' contained in (prime*m)!. Thus, if m is the smallest
integer such that max_power < s(m), then n = prime*m - 1.
:param max_power: A positive integer.
:param prime: A prime number.
:return: Largest positive integer n such that prime^max_power exactly divides n!.
"""
# Summation sequence s(m) of OrderSearch.powers_sequence
s = 1
m = 1
while not max_power < s:
# If max_power < s, then we add the next term of the sequence to s, i.e. consider s(m+1)
m += 1
s += powers_sequence(prime, m)
return prime * m - 1
[docs]
def smallest_factorial_from_bound(bound: int) -> int:
"""
Given a positive integer 'bound' it returns the largest integer n such that n! <= bound.
:param bound: A positive integer.
:return: The largest integer n such that n! <= bound.
"""
n = 1
while bound > 0:
n += 1
bound //= n
return n-1
[docs]
@order_search_logger
def candidates_AA(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all simple alternating groups :math:`\\mathrm{A}_n` whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
group_candidates = []
n_candidates = []
# Since the order of a simple alternating group is n!/2 (consider n > 4), the largest A_n compatible with the given
# prime powers is limited by the smallest missing prime in the list. So we cut the list to only the relevant powers.
if 0 in prime_bounds:
prime_bounds = prime_bounds[:prime_bounds.index(0)]
# The smallest simple alternating group is A_5, with order [2,1,1], so we check it is compatible
if len(prime_bounds) < 3:
return []
if prime_bounds[0] < 2:
return []
# Instead of looking for an n!/2 satisfying our bounds, we look for an n! satisifying 2 * our bounds, so
prime_bounds[0] += 1
# The first limiting factor is the smallest prime p not dividing the order, so n is at most p-1
n_candidates.append(ph.nth_prime(len(prime_bounds)+1)-1)
# For each power 'm' in prime_bounds corresponding to a prime 'p', we find the largest n such that p ** m exactly divides n!
for i in range(len(prime_bounds)):
n_candidates.append(candidate_from_power(prime_bounds[i], ph.nth_prime(i+1)))
# If there is an absolute bound, we compute the largest n such that n! < abs_bound and add it to the candidates
if abs_bound is not None:
n_candidates.append(smallest_factorial_from_bound(abs_bound*2))
# The n compatible with all restrictions is the smallest in n_candidates
for n in range(5, min(n_candidates)+1):
if return_codes:
group_candidates.append("AA-{0}".format(n))
else:
group_candidates.append(sg.Alternating(n))
return group_candidates
[docs]
@order_search_logger
def candidates_CA(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all classical Chevalley :math:`A_n(q)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
group_candidates = []
primes = list(ph.primes(len(prime_bounds)))
for i, prime in enumerate(primes):
# Chevalley type A groups depend on n and q=prime**k, n>0
# If the allowed power of the prime is 0, it cannot appear, so we proceed to the next one
if prime_bounds[i] == 0:
continue
# We run through the allowed k range, upper bounded by prime_bounds[i]
for k in range(1, prime_bounds[i] + 1):
q = prime ** k
n = 1
# We will build the order of the product term iteratibly for each q
product_order = ([0 for _ in range(len(prime_bounds))], 1)
# Since the order contains q**(n*(n+1)/2) we have a stopping condition to test each n, furthermore,
# no other part of the product is divisible by q
while prime_bounds[i] >= k * n * (n + 1) // 2:
# We build the term of the product for the current n and scan its primes
term_order = ph.prime_scanner_local(q ** (n + 1) - 1, primes)
# We build product order now, notice it may have primes not found in it already
product_order = prod_scanned(term_order, product_order)
# We cannot modify product_order since we may need it in the next iteration,
# so we create a relative product order to take care of the gcd term
relative_product_order = product_order
# We multiply the relative order by 1/(gcd(n+1,q-1)), notice it divides q**2-1
if math.gcd(n + 1, q - 1) != 1:
gcd_scan = ph.prime_scanner_local(math.gcd(n + 1, q - 1), primes)
relative_product_order = div_scanned(relative_product_order, gcd_scan)
# We check prime factors compatibility
if not prime_bound_compatiblity(relative_product_order, prime_bounds):
# Relative order at most divides by q-1, if taking this out does not satisfy the bounds,
# then no other does for greater n's
max_relative_product_order = div_scanned(product_order,ph.prime_scanner_local(q-1, primes))
if not prime_bound_compatiblity(max_relative_product_order,prime_bounds):
break
n+=1
continue
# Notice: (q,n)=(2,1) and (q,n)=(3,1) are not simple groups
if (q == 2 and n == 1) or (q == 3 and n == 1):
n += 1
continue
if return_codes:
group_candidates.append("CA-{0}-{1}_{2}".format(n, prime, k))
else:
group_candidates.append(sg.ChevalleyA(n,(prime, k)))
# We increase n by 1
n += 1
# If given an absolute bound, we discard groups that surpas it
if not(abs_bound is None):
return absolute_bound_filter(group_candidates, abs_bound, return_codes)
return group_candidates
[docs]
@order_search_logger
def candidates_CB(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all classical Chevalley :math:`B_n(q)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
group_candidates = []
primes = list(ph.primes(len(prime_bounds)))
prime_gen = ph.primes(len(prime_bounds))
for i in range(len(prime_bounds)):
# Chevalley type B groups depend on n and q=prime**k, n>1
# Prime number to be used to construct the group parameter q
prime = next(prime_gen)
# If the allowed power of the prime is 0, it cannot appear, so we proceed to the next one
if prime_bounds[i] == 0:
continue
# We run through the allowed k range, upper bounded by prime_bounds[i]
for k in range(1, prime_bounds[i] // 4 + 1):
q = prime ** k
n = 2
# We will build the order of the product term iteratibly for each q (initializing at n=1)
product_order = ph.prime_scanner_local(q**2-1, primes)
# We multiply the order by 1/(gcd(2,q-1)), notice it divides q**2-1, which always appears as a factor
# If q is odd, gcd(2,q-1)=2, otherwise, gcd(2,q-1) = 1, so we can adjust the power of 2 in product_order
if prime != 2:
product_order[0][0] -= 1
# Since the order contains q**(n**2) we have a stopping condition to test each n, furthermore,
# no other part of the product is divisible by q
while prime_bounds[i] >= k * (n ** 2):
# We build the term of the product for the current n and scan its primes
term_order = ph.prime_scanner_local(q ** (2 * n) - 1, primes)
# We build product order now, notice it may have primes not found in it already
product_order = prod_scanned(term_order, product_order)
# We check prime factors compatibility
if not prime_bound_compatiblity(product_order, prime_bounds):
n+=1
break
# Notice: (q,n)=(2,2) is not a simple group
if q == 2 and n == 2:
n += 1
continue
if return_codes:
group_candidates.append("CB-{0}-{1}_{2}".format(n, prime, k))
else:
group_candidates.append(sg.ChevalleyB(n,(prime, k)))
# We increase n by 1
n += 1
# If given an absolute bound, we discard groups that surpas it
if not(abs_bound is None):
return absolute_bound_filter(group_candidates, abs_bound, return_codes)
return group_candidates
[docs]
@order_search_logger
def candidates_CC(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all classical Chevalley :math:`C_n(q)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
# The checks are the same as in Chevalley B groups, as their orders coincide, so we work from the candidates of CB
candidates = candidates_CB(prime_bounds, abs_bound= abs_bound, return_codes=True)
if return_codes:
return [code.replace("B","C") for code in candidates if code.split("-")[1] != "2"]
return [sg.simple_group(code.replace("B","C")) for code in candidates if code.split("-")[1] != "2"]
[docs]
@order_search_logger
def candidates_CD(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all classical Chevalley :math:`D_n(q)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
group_candidates = []
primes = list(ph.primes(len(prime_bounds)))
prime_gen = ph.primes(len(prime_bounds))
for i in range(len(prime_bounds)):
# Chevalley type D groups depend on n and q=prime**k, n>0
# Prime number to be used to construct the group parameter q
prime = next(prime_gen)
# If the allowed power of the prime is 0, it cannot appear, so we proceed to the next one
if prime_bounds[i] == 0:
continue
# We run through the allowed k range, upper bounded by prime_bounds[i]
for k in range(1, prime_bounds[i] + 1):
q = prime ** k
n = 4
# We will build the order of the product term iteratibly for each q (initializing at n=3)
product_order = ph.prime_scanner_local((q**2-1)*(q**4-1), primes)
# Since the order contains q**(n*(n-1)) we have a stopping condition to test each n, furthermore,
# no other part of the product is divisible by q
while prime_bounds[i] >= k * n * (n - 1):
# We build the term of the product for the current n and scan its primes
term_order = ph.prime_scanner_local(q ** (2*n - 2) - 1, primes)
# We build product order now, notice it may have primes not found in it already
product_order = prod_scanned(term_order, product_order)
# We cannot modify product_order since we may need it in the next iteration,
# so we create a relative product order to take care of the gcd term
# We have to consider the extra term q**n-1:
relative_product_order = prod_scanned(product_order, ph.prime_scanner_local(q**n-1, primes))
# We multiply the relative order by 1/(gcd(4,q**n-1))
# Notice that if q == 2**k then gcd(4,q**n-1)=1, as such
if prime != 2:
if math.gcd(4, q ** n - 1) == 2:
relative_product_order[0][0] -= 1
else:
relative_product_order[0][0] -= 2
# We check prime factors compatibility
if not prime_bound_compatiblity(relative_product_order, prime_bounds):
# Relative order at most divides by 4, if taking this out does not satisfy the bounds,
# then no other does for greater n's
max_relative_product_order = product_order
max_relative_product_order[0][0] -= 2
if not prime_bound_compatiblity(max_relative_product_order, prime_bounds):
break
n+=1
continue
if return_codes:
group_candidates.append("CD-{0}-{1}_{2}".format(n, prime, k))
else:
group_candidates.append(sg.ChevalleyD(n,(prime, k)))
# We increase n by 1
n += 1
# If given an absolute bound, we discard groups that surpas it
if not(abs_bound is None):
return absolute_bound_filter(group_candidates, abs_bound, return_codes)
return group_candidates
[docs]
def candidates_exceptional_chevalley(prime_bounds: list[int], abs_bound: int | None, q_power: int,
product_indices: list[int], gcd_value: int, group_id: str, return_codes: bool):
"""
Generic function with the procedure to find exceptional Chevalley groups whose order divides prime_bounds and is less
than or equal to abs_bound.
To understand the parameters refering to the group order, see the order table in `Wikipedia`_.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param q_power: Exponent of the q factor in the group order.
:param product_indices: Indices of the capital Pi notation in the group order.
:param gcd_value: In the group order, number corresponding to gcd(gcd_value, q-1).
:param group_id: The ID of the exceptional Chevalley group.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
.. _Wikipedia: https://en.wikipedia.org/wiki/List_of_finite_simple_groups#Summary
"""
# First compatibility check
if max(prime_bounds) < q_power:
return []
group_candidates = []
primes = list(ph.primes(len(prime_bounds)))
prime_gen = ph.primes(len(prime_bounds))
for i in range(len(prime_bounds)):
# Exceptional Chevalley groups depend on n and q=prime**k, n>0
# Prime number to be used to construct the group parameter q
prime = next(prime_gen)
# If the allowed power of the prime is 0, it cannot appear, so we proceed to the next one
if prime_bounds[i] == 0:
continue
# We run through the allowed k range, upper bounded by prime_bounds[i] and q**q_power dividing the order
# Notice no other part of the product is divisible by q
for k in range(1, prime_bounds[i] // q_power + 1):
q = prime ** k
# We will build the order of the product term
product_order = ph.prime_scanner_local(math.prod(q ** i - 1 for i in product_indices),
primes)
# We add 1/gcd(gcd_value,q-1) (gcd_value can be 1,2 or 3)
if gcd_value == 3:
if math.gcd(gcd_value, prime ** k - 1) != 1:
product_order[0][1] -= 1
elif gcd_value == 2:
if math.gcd(gcd_value, prime ** k - 1) != 1:
product_order[0][0] -= 1
# We check prime factors compatibility
if not prime_bound_compatiblity(product_order, prime_bounds):
continue
# Group G_2(2) is not simple
if group_id == "G2" and q == 2:
continue
if return_codes:
group_candidates.append("{0}-{1}_{2}".format(group_id, prime, k))
else:
group_candidates.append(sg.simple_group_ids()[group_id]((prime, k)))
# If given an absolute bound, we discard groups that surpas it
if not (abs_bound is None):
return absolute_bound_filter(group_candidates, abs_bound, return_codes)
return group_candidates
[docs]
@order_search_logger
def candidates_E6(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all exceptional Chevalley :math:`E_6(q)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
return candidates_exceptional_chevalley(prime_bounds, abs_bound, 36, sg.CHEVALLEY_E6_POWER_INDICES,
3, "E6",return_codes)
[docs]
@order_search_logger
def candidates_E7(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all exceptional Chevalley :math:`E_7(q)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
return candidates_exceptional_chevalley(prime_bounds, abs_bound, 63, sg.CHEVALLEY_E7_POWER_INDICES,
2, "E7",return_codes)
[docs]
@order_search_logger
def candidates_E8(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all exceptional Chevalley :math:`E_8(q)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
return candidates_exceptional_chevalley(prime_bounds, abs_bound, 120, sg.CHEVALLEY_E8_POWER_INDICES,
1, "E8",return_codes)
[docs]
@order_search_logger
def candidates_F4(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all exceptional Chevalley :math:`F_4(q)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
return candidates_exceptional_chevalley(prime_bounds, abs_bound, 24, sg.CHEVALLEY_F4_POWER_INDICES,
1, "F4",return_codes)
[docs]
@order_search_logger
def candidates_G2(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all exceptional Chevalley :math:`G_2(q)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
return candidates_exceptional_chevalley(prime_bounds, abs_bound, 6, sg.CHEVALLEY_G2_POWER_INDICES,
1, "G2",return_codes)
[docs]
@order_search_logger
def candidates_SA(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all classical Steinberg :math:`{}^2A_n(q^2)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
group_candidates = []
primes = list(ph.primes(len(prime_bounds)))
prime_gen = ph.primes(len(prime_bounds))
for i in range(len(prime_bounds)):
# Steinberg type A groups depend on n and q=prime**k, n>1
# Prime number to be used to construct the group parameter q
prime = next(prime_gen)
# If the allowed power of the prime is 0, it cannot appear, so we proceed to the next one
if prime_bounds[i] == 0:
continue
# We run through the allowed k range, upper bounded by prime_bounds[i]
for k in range(1, prime_bounds[i] + 1):
q = prime ** k
n = 2
# We will build the order of the product term iteratibly for each q, initialize at n=1
product_order = ph.prime_scanner_local(q ** 2 - 1, primes)
# Since the order contains q**(n*(n+1)/2) we have a stopping condition to test each n, furthermore,
# no other part of the product is divisible by q
while prime_bounds[i] >= k * n * (n + 1) / 2:
# We build the term of the product for the current n and scan its primes
term_order = ph.prime_scanner_local(q ** (n + 1) - (-1) ** (n + 1), primes)
# We build product order now, notice it may have primes not found in it already
product_order = prod_scanned(term_order, product_order)
# We cannot modify product_order since we may need it in the next iteration,
# so we create a relative product order to take care of the gcd term
relative_product_order = product_order
# We multiply the relative order by 1/(gcd(n+1,q+1)), notice it divides q**2-1
if math.gcd(n + 1, q + 1) != 1:
gcd_scan = ph.prime_scanner_local(math.gcd(n + 1, q + 1), primes)
relative_product_order = div_scanned(relative_product_order, gcd_scan)
# We check prime factors compatibility
if not prime_bound_compatiblity(relative_product_order, prime_bounds):
# Relative order at most divides by q+1, if taking this out does not satisfy the bounds,
# then no other does for greater n's
max_relative_product_order = div_scanned(product_order, ph.prime_scanner_local(q + 1, primes))
if not prime_bound_compatiblity(max_relative_product_order, prime_bounds):
break
n+=1
continue
# Notice: (q,n)=(2,2) is not a simple group
if q == 2 and n == 2:
n += 1
continue
if return_codes:
group_candidates.append("SA-{0}-{1}_{2}".format(n, prime, k))
else:
group_candidates.append(sg.Steinberg2A(n,(prime, k)))
# We increase n by 1
n += 1
# If given an absolute bound, we discard groups that surpas it
if not(abs_bound is None):
return absolute_bound_filter(group_candidates, abs_bound, return_codes)
return group_candidates
[docs]
@order_search_logger
def candidates_SD(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all classical Steinberg :math:`{}^2D_n(q^2)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
group_candidates = []
primes = list(ph.primes(len(prime_bounds)))
prime_gen = ph.primes(len(prime_bounds))
for i in range(len(prime_bounds)):
# Steinberg type D groups depend on n and q=prime**k, n>3
# Prime number to be used to construct the group parameter q
prime = next(prime_gen)
# If the allowed power of the prime is 0, it cannot appear, so we proceed to the next one
if prime_bounds[i] == 0:
continue
# We run through the allowed k range, upper bounded by prime_bounds[i]
for k in range(1, prime_bounds[i] + 1):
q = prime ** k
n = 4
# We will build the order of the product term iteratibly for each q (initializing at n=3)
product_order = ph.prime_scanner_local((q**2-1)*(q**4-1), primes)
# Since the order contains q**(n*(n-1)) we have a stopping condition to test each n, furthermore,
# no other part of the product is divisible by q
while prime_bounds[i] >= k * n * (n - 1):
# We build the term of the product for the current n and scan its primes
term_order = ph.prime_scanner_local(q ** (2*n - 2) - 1, primes)
# We build product order now, notice it may have primes not found in it already
product_order = prod_scanned(term_order, product_order)
# We cannot modify product_order since we may need it in the next iteration,
# so we create a relative product order to take care of the gcd term
# We have to consider the extra term q**n+1:
relative_product_order = prod_scanned(product_order, ph.prime_scanner_local(q**n+1, primes))
# We multiply the relative order by 1/(gcd(4,q**n+1))
# Notice that if q == 2**k then gcd(4,q**n+1)=1, as such
if prime != 2:
if math.gcd(4, q ** n + 1) == 2:
relative_product_order[0][0] -= 1
else:
relative_product_order[0][0] -= 2
# We check prime factors compatibility
if not prime_bound_compatiblity(relative_product_order, prime_bounds):
# Relative order at most divides by 4, if taking this out does not satisfy the bounds,
# then no other does for greater n's
max_relative_product_order = product_order
max_relative_product_order[0][0] -= 2
if not prime_bound_compatiblity(max_relative_product_order, prime_bounds):
break
n+=1
continue
if return_codes:
group_candidates.append("SD-{0}-{1}_{2}".format(n, prime, k))
else:
group_candidates.append(sg.Steinberg2D(n,(prime, k)))
# We increase n by 1
n += 1
# If given an absolute bound, we discard groups that surpas it
if not(abs_bound is None):
return absolute_bound_filter(group_candidates, abs_bound, return_codes)
return group_candidates
[docs]
@order_search_logger
def candidates_2E(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all exceptional Steinberg :math:`{}^2E_6(q^2)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
# First compatibility check
if max(prime_bounds) < 36:
return []
group_candidates = []
primes = list(ph.primes(len(prime_bounds)))
prime_gen = ph.primes(len(prime_bounds))
for i in range(len(prime_bounds)):
# Exceptional Steinberg 2E6 groups depend on q=prime**k
# Prime number to be used to construct the group parameter q
prime = next(prime_gen)
# If the allowed power of the prime is 0, it cannot appear, so we proceed to the next one
if prime_bounds[i] == 0:
continue
# We run through the allowed k range, upper bounded by prime_bounds[i] and q**36 dividing the order
# Notice no other part of the product is divisible by q
for k in range(1, prime_bounds[i] // 36 + 1):
q = prime ** k
# We will build the order of the product term
product_order = ph.prime_scanner_local(math.prod(q ** i - (-1) ** i for i in sg.STEINBERG_2E6_POWER_INDICES),
primes)
# We add 1/gcd(gcd_value,q-1) (gcd_value can be 1,2 or 3)
if math.gcd(3, prime ** k + 1) != 1:
product_order[0][1] -= 1
# We check prime factors compatibility
if not prime_bound_compatiblity(product_order, prime_bounds):
continue
if return_codes:
group_candidates.append("2E-{0}_{1}".format(prime, k))
else:
group_candidates.append(sg.ExcetionalSteinberg2E6((prime, k)))
# If given an absolute bound, we discard groups that surpas it
if not (abs_bound is None):
return absolute_bound_filter(group_candidates, abs_bound, return_codes)
return group_candidates
[docs]
@order_search_logger
def candidates_3D(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all exceptional Steinberg :math:`{}^3D_4(q^3)`, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
# First compatibility check
if max(prime_bounds) < 12:
return []
group_candidates = []
primes = list(ph.primes(len(prime_bounds)))
prime_gen = ph.primes(len(prime_bounds))
for i in range(len(prime_bounds)):
# Exceptional Steinberg 3D4 groups depend on q=prime**k
# Prime number to be used to construct the group parameter q
prime = next(prime_gen)
# If the allowed power of the prime is 0, it cannot appear, so we proceed to the next one
if prime_bounds[i] == 0:
continue
# We run through the allowed k range, upper bounded by prime_bounds[i] and q**12 dividing the order
# Notice no other part of the product is divisible by q
for k in range(1, prime_bounds[i] // 12 + 1):
q = prime ** k
# We build the order of the group except for q**12
product_order = ph.prime_scanner_local(math.prod([q ** 8 + q ** 4 + 1, q ** 6 - 1, q ** 2 - 1]), primes)
# We check prime factors compatibility
if not prime_bound_compatiblity(product_order, prime_bounds):
continue
if return_codes:
group_candidates.append("3D-{0}_{1}".format(prime, k))
else:
group_candidates.append(sg.ExcetionalSteinberg3D4((prime, k)))
# If given an absolute bound, we discard groups that surpas it
if not (abs_bound is None):
return absolute_bound_filter(group_candidates, abs_bound, return_codes)
return group_candidates
[docs]
@order_search_logger
def candidates_SZ(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all Suzuki :math:`{}^2B_2(2^{2n+1})` groups, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
# Suzuki groups all have a power 2**6
if prime_bounds[0] < 6:
return []
group_candidates = []
primes = list(ph.primes(len(prime_bounds)))
# Since q=2**(2n+1) for Suzuki grups, we have q**2 = q**(4n+2), we start at n=1
n = 1
while 4 * n + 2 <= prime_bounds[0]:
q = 2 ** (2 * n + 1)
# We build the order of the group except for q**2
product_order = ph.prime_scanner_local((q ** 2 + 1) * (q - 1), primes)
# We check prime factors compatibility
if not prime_bound_compatiblity(product_order, prime_bounds):
n += 1
continue
if return_codes:
group_candidates.append("SZ-{0}".format(n))
else:
group_candidates.append(sg.Suzuki(n))
n += 1
# If given an absolute bound, we discard groups that surpas it
if not (abs_bound is None):
return absolute_bound_filter(group_candidates, abs_bound, return_codes)
return group_candidates
[docs]
@order_search_logger
def candidates_RF(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all Ree :math:`{}^2F_4(2^{2n+1})` groups, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
# Ree 2F4 groups all have a power 2**36
if prime_bounds[0] < 36:
return []
group_candidates = []
primes = list(ph.primes(len(prime_bounds)))
# Since q=2**(2n+1) for Suzuki grups, we have q**12 = q**(24n+12), we start at n=1
n = 1
while 24 * n + 12 <= prime_bounds[0]:
q = 2 ** (2 * n + 1)
# We build the order of the group except for q**12
product_order = ph.prime_scanner_local(math.prod([q ** 6 + 1, q ** 4 - 1, q ** 3 + 1, q - 1]), primes)
# We check prime factors compatibility
if not prime_bound_compatiblity(product_order, prime_bounds):
n += 1
continue
if return_codes:
group_candidates.append("RF-{0}".format(n))
else:
group_candidates.append(sg.Ree2F4(n))
n += 1
# If given an absolute bound, we discard groups that surpas it
if not (abs_bound is None):
return absolute_bound_filter(group_candidates, abs_bound, return_codes)
return group_candidates
[docs]
@order_search_logger
def candidates_TT(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns the Tits group :math:`{}^2F_4(2)'` if its order divides prime_bounds and is less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
# We check prime order compatibility
try:
for i in range(len(sg.TITS_ORDER)):
if sg.TITS_ORDER[i] > prime_bounds[i]:
return []
except IndexError:
# If we have more elements in sg.TITS_ORDER than in prime_bounds, the code raises IndexError, meaning we are
# considering too few primes, so it is not a candidate.
return []
# Check the absolute bound:
if not(abs_bound is None):
if ph.prime_reconstructor(sg.TITS_ORDER, 1) > abs_bound:
return []
if return_codes:
return ["TT"]
return [sg.Tits()]
[docs]
@order_search_logger
def candidates_RG(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all Ree :math:`{}^2G_2(3^{2n+1})` groups, simple groups of Lie type, whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
# Ree 2G2 groups all have a power 3**9
if prime_bounds[1] < 9:
return []
group_candidates = []
primes = list(ph.primes(len(prime_bounds)))
# Since q=2**(2n+1) for Suzuki grups, we have q**3 = q**(6n+3), we start at n=1
n = 1
while 6 * n + 3 <= prime_bounds[1]:
q = 3 ** (2 * n + 1)
# We build the order of the group except for q**12
product_order = ph.prime_scanner_local((q ** 3 + 1) * (q - 1), primes)
# We check prime factors compatibility
if not prime_bound_compatiblity(product_order, prime_bounds):
n += 1
continue
if return_codes:
group_candidates.append("RG-{0}".format(n))
else:
group_candidates.append(sg.Ree2G2(n))
n += 1
# If given an absolute bound, we discard groups that surpas it
if not (abs_bound is None):
return absolute_bound_filter(group_candidates, abs_bound, return_codes)
return group_candidates
[docs]
@order_search_logger
def candidates_SP(prime_bounds: list[int], abs_bound = None, return_codes = True):
"""
Returns a list of all sporadic simple groups whose order divides prime_bounds and is
less than or equal to abs_bound.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code. See SimpleGroups.simple_group_ids.
:return: Returns a list, the contents depend on the return_codes parameter.
"""
sporadic_orders = {group["id"]: group["order"] for group in sg.sporadic_groups_data()}
group_candidates = []
for group in sporadic_orders:
# We check prime order compatibility
candidate = True
try:
for i, prime_power in enumerate(sporadic_orders[group]):
if prime_power > prime_bounds[i]:
candidate = False
break
except IndexError:
# If we have more elements in sporadic_orders[group] than in prime_bounds, the code raises IndexError,
# meaning we are considering too few primes, so it is not a candidate.
continue
if not candidate:
continue
# Check the absolute bound:
if abs_bound is not None and (ph.prime_reconstructor(sporadic_orders[group], 1) > abs_bound):
continue
if return_codes:
group_candidates += [f"SP-{group}"]
else:
group_candidates += [sg.Sporadic(group)]
return group_candidates
[docs]
def clear_duplicates(groups: list, codes: bool) -> list:
"""
Given a list of simple group objects or a list of simple group codes, returns a new list clear of duplicates.
:param groups: List of simple groups, either codes or objects, not mixed.
:param codes: True if 'groups' contains codes, False if it contains simple group objects.
:return: A list of simple groups (objects or codes depending on 'codes') free of duplicates.
"""
duplicate_free = []
# For the duplication cleaning, we work with the group codes
if codes:
codes_list = [sg.simple_group(code).normalized_code() for code in groups]
else:
codes_list = [group.normalized_code() for group in groups]
for code in codes_list:
# First we check if the same code is duplicated
if code in duplicate_free:
continue
# We obtain the isomorphic codes
isomorphic_codes = sg.simple_group(code).isomorphisms()
if not isomorphic_codes:
# If there are no isomorphisms, the group cannot be a duplicate
duplicate_free.append(code)
else:
duplicate = False
# We check if any of the isomorphic codes is already present in the list
for code_iso in isomorphic_codes:
if code_iso in duplicate_free:
# If we find the code in the list, we mark it as a duplicate and break
duplicate = True
break
# If we reach the end of the process and the code has not been found, we add it
if not duplicate:
duplicate_free.append(code)
# We return a list of codes or simple group objects depending on the original input
if not codes:
return [sg.simple_group(code) for code in duplicate_free]
return duplicate_free
CANDIDATE_FUNCTIONS = {"AA": candidates_AA,
"CA": candidates_CA,
"CB": candidates_CB,
"CC": candidates_CC,
"CD": candidates_CD,
"E6": candidates_E6,
"E7": candidates_E7,
"E8": candidates_E8,
"F4": candidates_F4,
"G2": candidates_G2,
"SA": candidates_SA,
"SD": candidates_SD,
"2E": candidates_2E,
"3D": candidates_3D,
"SZ": candidates_SZ,
"RF": candidates_RF,
"TT": candidates_TT,
"RG": candidates_RG,
"SP": candidates_SP}
[docs]
def simple_group_by_order(prime_bounds: list[int], abs_bound: int|None=None, return_codes: bool=True, ignore: list[str]|None=None)\
-> list[str|sg.SimpleGroup]:
"""
Returns a list of all simple groups whose order divides prime_bounds and is less than or equal to abs_bound.
Certain groups can be ignored by providing a list of ids to ignore. See :py:func:`FiSGO.SimpleGroups.simple_group_ids`
for a list of valid ids. By default, no groups are ignored.
:param prime_bounds: Contains a list with the maximum powers for each prime, example: [2,0,1,0,3] means the order
of the group must divide 2^2*5^1*11^3.
:param abs_bound: An integer providing an upper bound to the order of the group, if None, then it is constructed
from prime_bounds.
:param return_codes: If False, the function returns a list of derived objects from the SimpleGroup class. If True,
the function returns a list of strings, each string represents a simple group code.
See :py:func:`FiSGO.SimpleGroups.simple_group_ids`.
:param ignore: List of groups to ignore given as a list of code ids, example: ['AA', 'SP']. For all ids see
:py:func:`FiSGO.SimpleGroups.simple_group_ids`. By default, no groups are ignored.
:return: A list of simple groups (objects or codes depending on 'return_codes') free of duplicates.
"""
if ignore is None:
ignore = []
group_candidates = []
for group_type in CANDIDATE_FUNCTIONS:
if group_type in ignore:
continue
group_candidates += CANDIDATE_FUNCTIONS[group_type](prime_bounds, abs_bound=abs_bound, return_codes=return_codes)
return clear_duplicates(group_candidates, return_codes)