Skip to content

NC versions of PreImages, PreImagesSet, PreImagesElm and PreImagesRepresentative - version 2#6409

Open
cdwensley wants to merge 59 commits into
masterfrom
preimrep2
Open

NC versions of PreImages, PreImagesSet, PreImagesElm and PreImagesRepresentative - version 2#6409
cdwensley wants to merge 59 commits into
masterfrom
preimrep2

Conversation

@cdwensley

@cdwensley cdwensley commented May 20, 2026

Copy link
Copy Markdown
Contributor

This aims to continue work on the process outlined in issue #4809, and started in PR #5073.
(The latter PR was so far out of sync with the master that rebasing has proved to be very difficult, hence this version.)
The operations PreImages, PreImagesSet, PreImagesElm and PreImagesRepresentative have all been renamed throughout the library by adding 'NC' to their names. New versions of the four operations have been introduced which just add a simple test and then call the NC versions.
Authors of packages which include a method for one of the four operations have been asked to adjust their packages to prepare for the change. The packages which have merged a PR, and made a release after that are: cryst, fr, orb, polycyclic, matgrp, qpa, rcwa, semigroups, utils and wedderga, while fga has merged a PR but not made a release.
The fining package has closed PR#28 without merging it - it installs another method for PreImagesSet.

@codecov

codecov Bot commented May 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 61.35458% with 485 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.95%. Comparing base (3b1e5bc) to head (fc31d4e).

Files with missing lines Patch % Lines
lib/mapprep.gi 55.96% 96 Missing ⚠️
lib/mapphomo.gi 55.55% 48 Missing ⚠️
lib/mapping.gi 56.97% 37 Missing ⚠️
lib/ghomperm.gi 48.38% 32 Missing ⚠️
lib/gprdperm.gi 48.97% 25 Missing ⚠️
lib/ghom.gi 55.55% 20 Missing ⚠️
lib/relation.gi 73.97% 19 Missing ⚠️
lib/mgmring.gi 63.63% 16 Missing ⚠️
lib/alghom.gi 58.33% 15 Missing ⚠️
lib/field.gi 41.66% 14 Missing ⚠️
... and 33 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6409      +/-   ##
==========================================
- Coverage   79.03%   78.95%   -0.08%     
==========================================
  Files         685      685              
  Lines      293517   294316     +799     
  Branches     8664     8664              
==========================================
+ Hits       231987   232390     +403     
- Misses      59729    60122     +393     
- Partials     1801     1804       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@stertooy stertooy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very happy to see this PR revived!

One thing I'm a bit wary of, is how PreImagesSet( f, U ) always returns [ ] if U is not a subset of Range( f ), even if they have non-empty intersection. Intuitively, I would expect this function to follow the standard(?) mathematical definition of $f^{-1}(U)$, so something like

PreImagesSetNC( Intersection( Range( f ), U ) )

Comment thread lib/ghomfp.gi Outdated
@cdwensley

Copy link
Copy Markdown
Contributor Author

@stertooy It's so good to get feedback on this work - I will get on with making the changes you suggest.
The way I have done it most of the time, when the new test fails, PreImagesRepresentative returns fail, but PreImagesSet and PreImagesElm (which expect a list) return []. Is that correct, or should this be fail also for PreImagesElm? With PreImagesSet I can see that Intersection( Range(f),U) should be used.

@cdwensley

Copy link
Copy Markdown
Contributor Author

Now I am really confused. The manual states: "If elms is a subset of the range of the general mapping map then PreImagesSet returns the set of all preimages of elms under map." Sticking to that, PreImagesSet should return fail if U is not a subset of the range. Alternatively, we could ditch PreImagesSetNC, and redefine PreImagesSet to return the preimage of the intersection, which could be [].

@cdwensley

cdwensley commented May 21, 2026

Copy link
Copy Markdown
Contributor Author

Perhaps this is why I returned [] in PreImagesElm when the test failed:
Two existing methods for PreImagesElm in mapphomo.gi call PreImagesRepresentative,
and if that returns fail then [] is returned.
I'll do nothing now until advice is received.

Comment thread lib/alghom.gi Outdated
@stertooy

Copy link
Copy Markdown
Contributor

The manual states: "If elms is a subset of the range of the general mapping map then PreImagesSet returns the set of all preimages of elms under map." Sticking to that, PreImagesSet should return fail if U is not a subset of the range.

I think the question is what we want the non-NC functions to do for elements/sets not contained in the range. I see two options:

  1. We limit the scope of these functions to elements/sets contained in the range. The only difference between NC and non-NC functions, is that the latter are guaranteed to return fail if the element/set is not contained in the image.

  2. The non-NC functions behave like the usual mathematical definition of a preimage. So in this case PreImagesElm( f, x ) would return the empty set [ ] if x is not contained in the image of f, and PreImagesSet( f, U ) would return the same as PreImagesSetNC( f, Intersection( Image( f ), U ) ).

The former option keeps the NC and non-NC versions closer together, with the only difference being a extra check, which is what an NC version usually indicates in GAP. The latter option sticks closer to the standard mathematical definitions. Either option is fine with me (with a preference for option 2), but it should at least be consistent across all the affected functions.

Alternatively, we could ditch PreImagesSetNC, and redefine PreImagesSet to return the preimage of the intersection, which could be [].

I think we should still keep PreImagesNC in this case, since certain functions/methods may have prior knowledge of a set actually being contained in the image, and they can then use the NC version to avoid running a superfluous membership check or intersection calculation.

Perhaps @fingolfin @ThomasBreuer @hulpke want to weigh in, given that they were active in #4809 and #5173?

@fingolfin

Copy link
Copy Markdown
Member

Thanks for working on this again, @cdwensley. Unfortunately I don't have the bandwidth available to read all here, think about it, and come up with a good stance right now -- we'll get there, but I am afraid not in time for GAP 4.16.0 -- but I am confident it'll get merged eventually, certainly before 4.17.0.

I'll try to get back to this when I have some mental capacity for it

@hulpke

hulpke commented May 28, 2026

Copy link
Copy Markdown
Contributor

I think part of the reason of NC functions is to actually not have to worry about issues such as membership (which might require an extra test).
I.e. the result of PreImagesSetNC should undefined if the set is not in the image of the map, but if it is returns the same as PreImagesSet.

@cdwensley

cdwensley commented May 28, 2026 via email

Copy link
Copy Markdown
Contributor Author

@cdwensley

cdwensley commented May 28, 2026 via email

Copy link
Copy Markdown
Contributor Author

fingolfin and others added 10 commits June 18, 2026 15:55
Teach args helper about multi-arg variadic functions.
…s were not supported (#6401)

Add runtime argument checks to the user-facing GF2 and 8-bit
kernel entry points instead of relying on debug-only assertions or
unchecked representation assumptions.

Use shared Require helpers for compressed vectors and matrices,
and tighten plain-list validation where these entry points expect
list-backed inputs.

Harden the exported vec8bit kernel entry points that assumed a
first row existed, and fix SHIFT_VEC8BIT_RIGHT for empty
compressed vectors.

AI assistance from OpenAI Codex helped prepare these kernel
validation changes.

Co-authored-by: Codex <codex@openai.com>
Resolves #2570

Co-authored-by: Codex <codex@openai.com>
If input group is infinite or not solvable, fail is returned.
Mostly avoid putting to many things on one line, for better
readability and more accurate code coverage tracking.

Also remove obsolete restrictions for perfect group
orders 86016, 368640, 737280
This undocumented function accepted more than one argument, but
ignored any beyond the first. Apparently this was introduced in
the GAP 4.5 days for compatibility reasons? But compatibility
with what? No package known to me calls this.

For reference in GAP 4.4 it had two arguments (but also was
undocumented, and not called by any outside code).
The OrbitStabilizer entry in the reference manual only documented the
algorithm, not what the operation returns. Adds:

- A short sentence explicitly stating the return value is an immutable
  record with `orbit` (the orbit as a list) and `stabilizer` (the point
  stabilizer subgroup of G).
- A small example block using the same `g:=Group((1,3,2),(2,4,3));;`
  setup already used by the adjacent `Stabilizer` example, so the
  rendered output is identical to verified output already in this file.
fingolfin and others added 17 commits June 18, 2026 15:55
Also remove some now redundant methods.

Before:

    gap> m:=RandomMat(256, 256, GF(1000000007));;
    gap> mat:=Matrix(IsZmodnZMatrixRep, GF(1000000007), m);;
    gap> RankMat(mat)=256; time;
    true
    170
    gap> DeterminantMat(mat); time;
    ZmodpZObj( 873487643, 1000000007 )
    23982

After:

    gap> m:=RandomMat(256, 256, GF(1000000007));;
    gap> mat:=Matrix(IsZmodnZMatrixRep, GF(1000000007), m);;
    gap> RankMat(mat)=256; time;
    true
    161
    gap> DeterminantMat(mat); time;
    ZmodpZObj( 873487643, 1000000007 )
    1949
* Align and clarify break loop instructions further
* Adjust <Log> blocks to new late error msg
* Regenerate tst/testspecial
They are not referenced anywhere and nowadays at
most of historical interest.
It does not seem to serve any purpose (anymore?)
There was an incorrect comment claiming that SyGAPCRC only ever
returns 0 in case of an error. There was also code that looked as
if it ensured that (by checking if `crc` is 0 and if so replacing
it by 1). But this code in fact was a no-op, and has been one
since 1997, when the code was changed to return `crc >> 4` instead
of `crc` -- yet both `0 >> 4` and `1 >> 4` are equal to 0.

Regarding the other changes:
- `(crc >> 8) & 0x00FFFFFFL` is the same as `crc >> 8` since `crc`
  is an unsigned 32 bit value
- removed a comment from the 1990ies suggesting that `SyGAPCRC`
  should ignore whitespace and comment changes; but we will
  never do that (as it would be very difficult, break backwards
  compatibility, and have little practical use)
- align right shift in SyGAPCRC and FuncCrcString: one was
  "emulating" a signed shift, the other didn't. This didn't cause
  a problem in practice because essentially every compiler in
  active uses nowadays implements signed right shifts; but the C
  standard does not require this. Now both use the same branchless
  emulation code which e.g. clang compiles down into a single
  instruction (GCC, MSVC, ICC do not, but that's fine, this code
  is not a bottleneck for anything)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Also treat readline headers as system includes. This avoids
Homebrew readline header warnings under --enable-Werror.

Co-authored-by: Max Horn <max@quendi.de>
Co-authored-by: Codex <codex@openai.com>
In case someone uses '%g' in a format string but fails
to provide a matching argument.

Moreover, make sure we don't use `arg2` more then once.
Comment thread lib/vspchom.gi.changed Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file was added by accident and should be removed again.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@ThomasBreuer ThomasBreuer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to now, PreImagesSomething( map, obj ) is defined as follows.
If obj is not an element (or subset, respectively) of Range( map ) then anything may happen.
If obj is an element (or subset, respectively) of Range( map ) but not of Image( map ) then fail is returned.
Otherwise, an element (or subset, respectively) of Source( map ) is returned that maps to obj under map.

The idea of the current pull request is to define functions PreImagesSomethingNC with the above definition, and to change the functions PreImagesSomething to signal an error if obj is not an element (or subset, respectively) of Range( map ), and to do the same as PreImagesSomethingNC otherwise.
Thus an error message is guaranteed for PreImagesSomething in the former case, and the user can decide to omit the membership test in Range( map ) by calling PreImagesSomethingNC.
(The text for the release notes would look like that if the pull request gets merged in this form.)

The proposed changes do not address the problems in the example from #4809 and in the second example from #4088, where one asks for the preimage of an element that is in the range but not in the image of the map.
My understanding of the discussion in #4088 was that the membership test for obj in the image of map is what one wants to skip in the new NC variants.
(If this is the case then it will be not difficult to change the pull request accordingly.)

The documentation of the functions should say that fail is returned if obj is not an element (or subset) of the range of map. Currently the proposed text says that the membership is tested.
(An alternative to a fail result would be an error. Of course, if we change the code such that membership in the image of map is tested, then a fail result is correct.)

Comment thread lib/alglie.gi Outdated
if not ( x in Range( f ) ) then
return fail;
fi;
return PreImagesRepresentative( f, x );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return PreImagesRepresentative( f, x );
return PreImagesRepresentativeNC( f, x );

(The old code should run into an infinite loop, which means that there is no test example for this method.)

Comment thread lib/field.gi Outdated
if not (elm in Range(hom)) then
return fail;
fi;
return PreImagesElm( hom, elm );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return PreImagesElm( hom, elm );
return PreImagesElmNC( hom, elm );

(Again, there seems to be no test example.)

Comment thread lib/field.gi Outdated
if not IsSubset( Range(hom), elms ) then
return fail;
fi;
return PreImagesSet( hom, elms );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return PreImagesSet( hom, elms );
return PreImagesSetNC( hom, elms );

Comment thread lib/liefam.gi Outdated
if not ( elm in Range(emb) ) then
return fail;
fi;
return PreImagesElm( emb, elm );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return PreImagesElm( emb, elm );
return PreImagesElmNC( emb, elm );

Comment thread lib/mapping.gd Outdated
## <P/>
## Anything may happen if <A>elm</A> is not an element of the range of
## <A>map</A>.
## From &GAP; version 4.16.0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adjust to the GAP version number in question

Comment thread lib/relation.gi Outdated
if not ( n in Range(rel) ) then
return fail;
fi;
return PreImagesElm( rel, n );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return PreImagesElm( rel, n );
return PreImagesElmNC( rel, n );

Comment thread lib/mapping.gd Outdated
## From &GAP; version 4.16.0
## <C>PreImagesElm</C> was renamed <C>PreImagesElmNC</C>
## throughout the library, and <C>PreImagesElm</C> now checks that
## <A>elm</A> is an element of the range before calling <C>PreImagesElmNC</C>.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The definition should say what happens if this check fails.

Comment thread lib/mapping.gd Outdated
## From &GAP; version 4.16.0 <C>PreImagesRepresentative</C>
## was renamed <C>PreImagesRepresentativeNC</C> throughout the library,
## and <C>PreImagesRepresentative</C> now checks that <A>elm</A> is an
## element of the range before calling <C>PreImagesRepresentativeNC</C>.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The definition should say what happens if this check fails.

Comment thread lib/mapping.gd Outdated
## From &GAP; version 4.16.0 <C>PreImagesSet</C>
## has been renamed <C>PreImagesSetNC</C> throughout the library,
## and <C>PreImagesSet</C> now checks that <A>elms</A> is a
## subset of the range before calling <C>PreImagesSetNC</C>.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The definition should say what happens if this check fails.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many thanks for all these suggestions and making it clear how to proceed.
I have dealt with most of these suggestions, but there is more work to do.
Are we now agreed that, for the non-NC versions:

  • elm/elms/coll not in the range => an Error is thrown, and
  • elm/elms/coll in the range but not the image => fail is returned?
    That being the case, I am adding error messages.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is how I understand the discussions in #4088 and #4809.

The "Error is thrown" part is changed behaviour in the sense that the current documentation promises nothing in this situation, hence the documentation should state that now an error is promised.

The "fail is returned" part is a bugfix in the sense that the documentation promises this but the current code does not follow this rule. (The examples in #4088 show that the current code may return a wrong element or run into an error.)

And the new NC variants allow one to skip the checks that are necessary to achieve these changes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have now added lots of error messages; changed a few [ ] into fail; and added a few more non-NC methods.
The examples in #4088 and #4809 now appear to correctly return fail.

Comment thread lib/mapping.gd Outdated
@cdwensley

Copy link
Copy Markdown
Contributor Author

Advice required about PreImagesSet. If R is the range of map, and if the set U is a subset of R such that the intersection of U and the image of map is V then, mathematically, PreImagesSet(U) is PreImagesSet(V). Is that what should be implemented? At present, fail is returned.

@ThomasBreuer

Copy link
Copy Markdown
Contributor

Advice required about PreImagesSet.

I see, PreImagesSet is conceptually different from PreImagesRepresentative.
(Everything is o.k. with the setup for PreImagesRepresentative: The checks are done by the methods for PreImagesRepresentative, then PreImagesRepresentativeNC gets called which does not do any check.)

The documentation of PreImagesSet says that anything may happen if the given set is not a subset of the Range of the map. Here it makes sense to do the same as for PreImagesRepresentative, that is, make a check and signal an error if the set is not a subset of the range.
For the case that the set is a subset of the Range of the map, we could keep the old definition of PreImagesSet, that is, return the set of all preimages of the set under the map, which may be empty.
The point is that with this definition, PreImagesSetNC must deal with the case that the given set is not necessarily a subset of the Image of the map.
Aren't we then back in the situation before this pull request, that is, that some methods don't do this? (I will have to look at the old code.)

@stertooy

stertooy commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

For the case that the set is a subset of the Range of the map, we could keep the old definition of PreImagesSet, that is, return the set of all preimages of the set under the map, which may be empty.
The point is that with this definition, PreImagesSetNC must deal with the case that the given set is not necessarily a subset of the Image of the map.
Aren't we then back in the situation before this pull request, that is, that some methods don't do this? (I will have to look at the old code.)

Is it not acceptable to have PreImagesSet( f, U ) do something like

if not IsSubset( Range( f ), U ) then
    Error( "..." );
fi;
V := Intersection( Image( f ), U );
return PreImagesSetNC( f, V );

or perhaps, if U is just a list without additional structure,

if not IsSubset( Range( f ), U ) then
    Error( "..." );
fi;
prei := Set( U, u -> PreImagesRepresentative( f, u ) );
RemoveSet( prei, fail );
return prei;

Something like this means we keep the old definition of PreImagesSet, while the NC versions never have to deal with things outside the image.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants