Display group hashes and identify groups from a description (LMFDB#5556) - #44
Display group hashes and identify groups from a description (LMFDB#5556)#44roed-math wants to merge 6 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>
|
All three merge blockers from the review are addressed, plus one problem the work turned up. 1. One hash resolution layerNew file
Everything user-facing goes through it: What the data actually looks like, since it decides the rule (verified over every row of each order):
At all ten One deviation, and it needs a decision. The review asked for the public hash at those orders to be read from 2. Parsing, construction and the order computation are inside the guard
The problem this turned up: an alarm that fires while GAP is starting up leaves 3. A real encoding for extension-field entries
Tests
|
…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>
|
Both remaining items are addressed in Complete hash searches now render the complete clusterNew route
So Also fixed: a unique complete-table match with neither a row nor a live page (the last bullet of the review's list) now displays its label instead of falling through to an empty search. Counters no longer answer structural-hash searches
{"$or": [{"hash": 1584677793794603025, "order": {"$nin": [512, 1152, 1536, 1920, 2187, 15625]}},
{"order": 512, "counter": {"$in": [11]}}]}from one indexed
I took the preferred option rather than rejecting unpinned hash searches, since the Optional columnThe limitation is now documented in the One crash found on the way
Tests and CI
On CI: the runs for |
…#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>
|
GPT signed off. |
|
Superseded by LMFDB#7177, opened upstream from this same branch. Closing here; review continues upstream. |
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 indexpage 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 FiniteGroups
Hash.m— using strict parsers that never eval user input,a 10^6 order cap, and alarm() timeouts. Results go through GAP identification (a proof), the
complete
gps_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 proveisomorphism. 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.
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 andquotient popups, in the identification tool and in the optional column. That matters because
gps_groups.hashstores the label counter rather than a hash at six of the ten orders with acomplete
gps_smallhashtable (512, 1152, 1536, 1920, 2187, 15625), so searches at those ordersresolve through
gps_smallhash, where the answer is provably complete and a unique match is aproof 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 ofGF(4).
Addresses LMFDB#5556.
🤖 Generated with Claude Code