back to table · edit · history · where entries came from · files · download
11020 bytes, as of the version from 2026-09-12 23:55. Recorded here, not run.
"""Orders of finite simple groups of Lie type -- numberdb.org/T220
This generator stores exact integer orders for the simple groups in the
Lie-type part of the classification, including the Tits group. The parameter
q is the one used in the order formula: for example ${}^2A_n(q^2)$ is indexed
here by q, so the same group is also written PSU_{n+1}(q).
Run it with SageMath:
$ sage -pip install numberdb # once
$ sage -python generate.py # check the table against this code
$ sage -python generate.py --publish # send it, with NUMBERDB_API_KEY set
The range is every group in the Lie-type families named by the family
parameter with q <= 32 and order below 10^100, the Tits group, and every
A_1(q) with prime-power q <= 1000.
"""
import os
import sys
from math import gcd
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
TABLE = "T220"
Q_LIMIT = 32
A1_Q_LIMIT = 1000
ORDER_LIMIT = 10 ** 100
TITS_ORDER = 17971200
EXCEPTIONAL_SYMBOLS = {
"E6": "E_6",
"E7": "E_7",
"E8": "E_8",
"F4": "F_4",
"G2": "G_2",
}
FAMILIES = (
"A", "B", "C", "D",
"E6", "E7", "E8", "F4", "G2",
"2A", "2D", "2E6", "3D4",
"2B2", "2F4", "2G2", "Tits",
)
def _key_from_stdin():
if os.environ.get("NUMBERDB_KEY_FROM_STDIN") != "1":
return
token = sys.stdin.read().strip()
if "=" in token and token.split("=", 1)[0].isupper():
token = token.split("=", 1)[1].strip().strip("'\"")
if token:
os.environ["NUMBERDB_API_KEY"] = token
def is_prime(n):
if n < 2:
return False
if n % 2 == 0:
return n == 2
d = 3
while d * d <= n:
if n % d == 0:
return False
d += 2
return True
def prime_powers(limit):
powers = set()
for p in range(2, limit + 1):
if not is_prime(p):
continue
q = p
while q <= limit:
powers.add(q)
q *= p
return sorted(powers)
def suzuki_ree_qs(base, limit):
out = []
q = base ** 3
while q <= limit:
out.append(q)
q *= base ** 2
return out
def product(values):
out = 1
for value in values:
out *= value
return out
def order(family, n, q):
if family == "A":
return (q ** (n * (n + 1) // 2)
* product(q ** (i + 1) - 1 for i in range(1, n + 1))
// gcd(n + 1, q - 1))
if family == "B" or family == "C":
return (q ** (n * n)
* product(q ** (2 * i) - 1 for i in range(1, n + 1))
// gcd(2, q - 1))
if family == "D":
return (q ** (n * (n - 1)) * (q ** n - 1)
* product(q ** (2 * i) - 1 for i in range(1, n))
// gcd(4, q ** n - 1))
if family == "E6":
return (q ** 36
* product(q ** i - 1 for i in (2, 5, 6, 8, 9, 12))
// gcd(3, q - 1))
if family == "E7":
return (q ** 63
* product(q ** i - 1 for i in (2, 6, 8, 10, 12, 14, 18))
// gcd(2, q - 1))
if family == "E8":
return q ** 120 * product(q ** i - 1 for i in (2, 8, 12, 14, 18, 20, 24, 30))
if family == "F4":
return q ** 24 * product(q ** i - 1 for i in (2, 6, 8, 12))
if family == "G2":
return q ** 6 * (q ** 2 - 1) * (q ** 6 - 1)
if family == "2A":
return (q ** (n * (n + 1) // 2)
* product(q ** (i + 1) - (-1) ** (i + 1) for i in range(1, n + 1))
// gcd(n + 1, q + 1))
if family == "2D":
return (q ** (n * (n - 1)) * (q ** n + 1)
* product(q ** (2 * i) - 1 for i in range(1, n))
// gcd(4, q ** n + 1))
if family == "2E6":
return (q ** 36
* product(q ** i - (-1) ** i for i in (2, 5, 6, 8, 9, 12))
// gcd(3, q + 1))
if family == "3D4":
return q ** 12 * (q ** 8 + q ** 4 + 1) * (q ** 6 - 1) * (q ** 2 - 1)
if family == "2B2":
return q ** 2 * (q ** 2 + 1) * (q - 1)
if family == "2F4":
return q ** 12 * (q ** 6 + 1) * (q ** 4 - 1) * (q ** 3 + 1) * (q - 1)
if family == "2G2":
return q ** 3 * (q ** 3 + 1) * (q - 1)
if family == "Tits":
return TITS_ORDER
raise ValueError("unknown family %s" % family)
def excluded(family, n, q):
return (
(family == "A" and n == 1 and q in (2, 3))
or (family == "2A" and n == 2 and q == 2)
or (family == "B" and n == 2 and q == 2)
or (family == "G2" and q == 2)
or (family in ("2B2", "2F4") and q == 2)
or (family == "2G2" and q == 3)
)
def rank_range(family):
if family == "A":
return range(1, 80)
if family == "2A":
return range(2, 80)
if family == "B":
return range(2, 80)
if family == "C":
return range(3, 80)
if family in ("D", "2D"):
return range(4, 80)
fixed = {
"E6": 6, "2E6": 6, "E7": 7, "E8": 8, "F4": 4, "G2": 2,
"3D4": 4, "2B2": 2, "2G2": 2, "2F4": 4, "Tits": 4,
}
return range(fixed[family], fixed[family] + 1)
def qs_for_table(family, n):
if family == "Tits":
return [2]
if family in ("2B2", "2F4"):
return suzuki_ree_qs(2, Q_LIMIT)
if family == "2G2":
return suzuki_ree_qs(3, Q_LIMIT)
limit = A1_Q_LIMIT if family == "A" and n == 1 else Q_LIMIT
return prime_powers(limit)
def table_parameters():
for family in FAMILIES:
for n in rank_range(family):
seen_below_limit = False
for q in qs_for_table(family, n):
if excluded(family, n, q):
continue
value = order(family, n, q)
if value >= ORDER_LIMIT and not (family == "A" and n == 1):
continue
seen_below_limit = True
yield {"family": family, "n": str(n), "q": str(q)}
if family in ("A", "2A", "B", "C", "D", "2D") and not seen_below_limit:
break
def group_name(family, n, q):
if family == "A":
return "$\\operatorname{PSL}_{%d}(%d)$" % (n + 1, q)
if family == "2A":
return "$\\operatorname{PSU}_{%d}(%d)$" % (n + 1, q)
if family == "B":
return "$B_{%d}(%d)$" % (n, q)
if family == "C":
return "$\\operatorname{PSp}_{%d}(%d)$" % (2 * n, q)
if family == "D":
return "$\\operatorname{P\\Omega}^{+}_{%d}(%d)$" % (2 * n, q)
if family == "2D":
return "$\\operatorname{P\\Omega}^{-}_{%d}(%d)$" % (2 * n, q)
if family == "Tits":
return "the Tits group ${}^2F_4(2)'$"
if family in EXCEPTIONAL_SYMBOLS:
return "$%s(%d)$" % (EXCEPTIONAL_SYMBOLS[family], q)
if family == "2E6":
return "${}^2E_6(%d^2)$" % q
if family == "3D4":
return "${}^3D_4(%d^3)$" % q
if family == "2B2":
return "${}^2B_2(%d)$" % q
if family == "2G2":
return "${}^2G_2(%d)$" % q
if family == "2F4":
return "${}^2F_4(%d)$" % q
return "$%s_{%d}(%d)$" % (family, n, q)
def comment(family, n, q):
parts = [group_name(family, n, q) + "."]
if family == "A" and n == 1 and q in (4, 5):
parts.append(
"This group is isomorphic to the alternating group "
"$\\operatorname{Alt}_5$.")
if family == "A" and n == 1 and q == 7:
parts.append("This group is isomorphic to $A_2(2)$.")
if family == "A" and n == 1 and q == 9:
parts.append(
"This group is isomorphic to the alternating group "
"$\\operatorname{Alt}_6$.")
if family == "A" and n == 2 and q == 2:
parts.append("This group is isomorphic to $A_1(7)$.")
if family == "A" and n == 2 and q == 4:
parts.append(
"It has the same order as $A_3(2)$ and is not isomorphic to it.")
if family == "A" and n == 3 and q == 2:
parts.append(
"This group is isomorphic to the alternating group "
"$\\operatorname{Alt}_8$.")
parts.append(
"It has the same order as $A_2(4)$ and is not isomorphic to it.")
if family == "2A" and n == 3 and q == 2:
parts.append("This group is isomorphic to $B_2(3)$.")
if family == "B" and n == 2 and q == 3:
parts.append("This group is isomorphic to ${}^2A_3(2^2)$.")
if family in ("B", "C") and n >= 3 and q % 2 == 0:
other = "C" if family == "B" else "B"
parts.append("It is isomorphic to $%s_{%d}(%d)$." % (other, n, q))
if family in ("B", "C") and n >= 3 and q % 2 == 1:
other = "C" if family == "B" else "B"
parts.append("It has the same order as $%s_{%d}(%d)$." % (other, n, q))
return " ".join(parts)
class LieTypeSimpleGroupOrders(numberdb.Generator):
table = TABLE
parameters = ("family", "n", "q")
type = "Z"
rigour = "exact"
def enumerate(self):
yield from table_parameters()
def value(self, params, digits):
family = str(params["family"])
n = int(params["n"])
q = int(params["q"])
return {
"number": ZZ(order(family, n, q)),
"comment": comment(family, n, q),
}
def fill_draft_once(generator, message):
"""Fill a fresh draft without the empty upsert probe."""
from numberdb._generate import (
_check_precision,
_check_rigour,
_producer,
_run_name,
_source_files,
)
from numberdb._write import Entries, attach, submit_entries, to_text
table = generator.table
run = _run_name(generator)
entries = Entries(*generator.parameters)
for params in generator.enumerate():
params = dict(params)
wanted = generator.digits_for(params)
entry = generator._entry(params, wanted)
value = entry["number"]
identity = ",".join(str(params[name]) for name in generator.parameters)
_check_rigour(generator, table, identity, value)
written = to_text(value, wanted, generator.format)
_check_precision(table, identity, written, wanted, lowering=False)
record = dict(entry)
record.pop("digits", None)
entries.add(**params, **record, digits=wanted)
answer = submit_entries(
table,
entries,
message=message,
produced_by=_producer(generator),
upsert=False,
run=run,
rigour=generator.rigour,
)
for name, body in sorted(_source_files(generator).items()):
attach(table, name, body, run=run, message=message,
rigour=generator.rigour)
return answer
if __name__ == "__main__":
_key_from_stdin()
generator = LieTypeSimpleGroupOrders()
if "--publish" in sys.argv or os.environ.get("NUMBERDB_PUBLISH") == "1":
print(fill_draft_once(
generator,
message="orders of finite simple groups of Lie type"))
else:
report = generator.verify(sample=None)
print(report)
sys.exit(0 if report.ok else 1)