Display group hashes and identify groups from a description - #7177
Display group hashes and identify groups from a description#7177roed-math wants to merge 8 commits into
Conversation
Part (a): surface the isomorphism-invariant hash in the finite groups interface. Abstract group homepages show the stored hash (with a search link) whenever it is informative, i.e. hash is present and differs from the label counter; add an optional, off-by-default "Hash" search column; and linkify the two hash TODO popups in group_data. Subgroup pages get nothing (subgroup_hash/quotient_hash are entirely NULL in the DB). Part (b): new /Groups/Abstract/identify endpoint plus a form on the index page and jump-box wiring for permutation input. The server computes the LMFDB (Magma) hash in-process via libgap -- a faithful port of Hash.m in new file identify.py -- with strict, no-eval parsers for permutation generators, PC codes and matrices, a 10^6 order cap, and cysignals alarm() timeouts. Lookup goes IdGroup (proof) -> gps_smallhash (complete tables) -> (order, hash) on gps_groups, with mathematically honest messaging (a hash match is not an isomorphism proof). Verified: libgap hash port reproduces every stored value tested (512.11, 1536.947, 2016.i, 6561.4, 5120.cs, 1024.a, M11, Sp(4,3), abelian cases); identify redirects/lists/errors and the alarm timeout+recovery exercised via the Flask test client; new pytest cases and the full test_abstract_groups.py (12) and test_browse_page.py (71) pass; pyflakes clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…FDB#5556) Review of PR 44 raised three merge blockers; this addresses all three. 1. New lmfdb/groups/abstract/hash_lookup.py is the single place that turns order#hash into a set of groups and decides which stored value may be shown as a hash. gps_groups.hash holds the label counter rather than the hash at six of the ten orders with a complete gps_smallhash table (512, 1152, 1536, 1920, 2187, 15625; checked over every row), so searches at those orders resolve through gps_smallhash, where the answer is complete and a unique match is a proof of isomorphism. The find box, the search page, group homepages, the subgroup and quotient popups, the identify tool and the optional column all go through the resolver, and a counter is never displayed under the group.hash knowl. A computed hash missing from a complete table now reports a data problem instead of rendering an empty paragraph. gps_smallhash has 4.2*10^8 rows and only an (order, hash) index, so the real hash of a group at those six orders cannot be fetched for display ((order, counter) is a 6-minute sequential scan); the value is suppressed rather than replaced by the counter, and the module says why. 2. identify_group parses, builds the group and computes its order inside one alarm, turns parser-side GAP or Sage failures into an error page, and checks the order cap before factoring the order. GAP's own start-up is deliberately outside the alarm: an alarm that fires during it leaves libgap unusable for the whole worker. 3. Matrix entries over GF(p^e) are integer codes 0 <= x < q whose base-p digits are coordinates in the power basis of the Conway generator, the convention of the FiniteGroups DecodeMat, so every element of GF(q) can be entered; Mat(1,4):[[2]] is the generator of GF(4) rather than zero. Verified with sage -python -m pytest on lmfdb/groups/abstract: test_abstract_groups.py 22 passed (8 new tests covering the three findings), test_browse_page.py 71 passed, pyflakes clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sh searches (LMFDB#5556) Follow-up review of PR 44 found the hash search still rendered through gps_groups, so it could not show what the complete tables know. A new route, /Groups/Abstract/hash/<order>/<value>, answers "which groups have this order and hash". At the ten orders with a complete gps_smallhash table it lists every one of them, including those with no database row (78125#3521944227884464685 is 78125.82, 78125.335, 78125.340, and only the last two are in gps_groups), says that the list is complete, links the ones with a homepage and marks the rest as absent, and redirects when the cluster determines a single group. Other orders go on to the ordinary search page, where the stored column is the hash. Every "groups with this order and hash" link now goes through the route: group homepages, subgroup and quotient popups, the identification tool and the find box, as does a search whose only constraints are an order and a hash. The hash column of gps_groups is no longer consulted at the six orders where it holds the label counter, whatever the order constraint: with no single order, hash_constraint builds an $or of the column match away from those orders together with one resolved (order, counter) branch per affected order, from a single indexed gps_smallhash query. So a search for the hash 11 no longer returns 512.11, whose stored 11 is its counter, while a search for 1584677793794603025 finds it with no order given at all. The optional column still cannot show a hash that only gps_smallhash knows, which is now documented; where the search itself named one hash at one order, that value is displayed. Out-of-range values are refused by the new route rather than reaching the database, which errors on them. Verified with sage -python -m pytest on lmfdb/groups/abstract: test_abstract_groups.py 25 passed (the hash tests now cover the complete cluster page, the popup and identification links into it, the label with no homepage, and the false-positive cases), test_browse_page.py 71 passed, pyflakes clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…#5556) collapse_ors() combines two $or conditions by updating each old branch with each new one, which discards the old condition wherever the two name the same column. They do here: an order list parses to {"$or": [{"order": 512}, {"order": 2016}]} and every branch of hash_constraint() names an order too, so the foiled query asked for the hash at every order except the six counter-storing ones, and looked up order-512 counters whether or not 512 was one of the orders asked for. merge_constraint() intersects the two conditions with $and instead, which is what SearchParser already does for negated inputs. searched_hash() read the order out of N#h without consulting the order box, so the early redirect to the hash page answered a request that names two different orders, which parse_hashes() rejects. It now returns nothing unless the two agree, and a range or a list of orders never agrees, so those go to the ordinary parser and its error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An order and a hash on their own are a lookup, not a search: the request the test made was redirected to the hash page and on to the homepage of 512.11, which shows no hash, and the assertion was met by the structural hash that the subgroup popups carry in their profiledata. A second condition keeps the request a table of results, where the column really does supply the hash that an order-512 row cannot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LMFDB uses American spelling, so "cancelled" becomes "canceled" in two comments and one assertion message. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Might be worth having knowls for permutation generators, PC code, and matrix generators which give more information on the main page. If I enter garbage, I do get a new page with a table which tells me what the options for entering data look like, but particularly with Matrices and PC code, I think it would be good to be more explicit up front with what those mean. |
|
Am I understanding the behavior correctly here: if I enter something beyond small groups it seems to give me a nice summary page with the hash explicitly listed and then a list of all groups with that hash? (I got one of these pages by going to the group 100000.o and copying its perm generators and entering that into the hash search) But if I enter a small group (something like |
GitHub Actions was disabled during a recent outage; this empty commit kicks the checks back into running.

Closes #5556.
Surfaces the isomorphism-invariant group hash in the finite groups interface and adds a group-identification tool.
Abstract group homepages now show the stored hash (with a search link) whenever it is informative (present and unequal to the label counter), there is an optional off-by-default "Hash" search column, and the two hash placeholders in the subgroup data popups are linkified.
A new
/Groups/Abstract/identifyendpoint (with a form on the index page and jump-box wiring for permutation input) lets a user enter permutation generators, a PC code, or matrix generators; the server computes the LMFDB (Magma) hash in-process via libgap, a faithful port of the FiniteGroupsHash.m, using strict parsers that never eval user input, a 10^6 order cap, andalarm()timeouts. Results go through GAP identification (a proof), the completegps_smallhashtables (a unique match is a proof), then an (order, hash) lookup ingps_groups, always with mathematically honest messaging that a hash match alone does not prove isomorphism.All hash resolution lives in one new module,
lmfdb/groups/abstract/hash_lookup.py, so thatN#hmeans the structural hash in the find box, on group homepages, in the subgroup and quotient popups, in the identification tool and in the optional column. That matters becausegps_groups.hashstores the label counter rather than a hash at six of the ten orders with a completegps_smallhashtable (512, 1152, 1536, 1920, 2187, 15625), so searches at those orders resolve throughgps_smallhash, where the answer is provably complete and a unique match is a proof of isomorphism. A stored counter is never displayed as a hash.Parsing, group construction and the order computation share one alarm, and the order cap is checked before the order is factored; GAP's own start-up is deliberately outside the alarm, since interrupting it leaves libgap unusable for the whole worker. Matrix entries over GF(p^e) are integer codes whose base-p digits are coordinates in the power basis of the Conway generator, matching the FiniteGroups
DecodeMatconvention, so all of GF(q) can be entered andMat(1,4):[[2]]is the generator of GF(4).Verified by reproducing many stored hashes against the database, exercising every input/lookup/error/timeout path through the Flask test client, and running the abstract-groups and browse-page test suites (pyflakes clean). Subgroup homepages are unchanged, because the subgroup/quotient hash columns are entirely NULL in the current data.
Ported from roed-math#44, where the full write-up and comment history live.
🤖 Generated with Claude Code