Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce flintify helper for "optimal" dispatch on integer and rational inputs #1867

Merged
merged 3 commits into from
Sep 26, 2024

Conversation

fingolfin
Copy link
Member

There are a bunch of FLINT function that come in multiple varieties. E.g. fmpz_add, fmpz_add_ui, fmpz_add_si all take one fmpz and one additional argument of type fmpz / UInt / Int, respectively.

So naturally we define different + and add! methods in Julia using them. But which method to invoke when adding a ZZRingElem and a Julia integer of a type different from UInt and Int, such as e.g. UInt32 ? In that case we currently always convert that into a ZZRingElem. But that's inefficient, it would be be better to convert that value to a UInt or Int value and then use the optimized fmpz_add_ui/_si method.

Of course this can be done, but getting it right is tedious, repetitive and it is easy to overlook a case.

This is where FlintInt comes in: this is defined as Union{Int, ZZRingElem}. We then provide constructors which convert any Integer to a FlintInt in an optimal way (at least for a bunch of built-in integer types).

This then can be used to write optimal dispatch for Integer types like this:

add!(x::ZRingElem, y::ZRingElem) = ...
add!(x::ZRingElem, y::Int) = ...
add!(x::ZRingElem, y::UInt) = ...
# fallback code
add!(x::ZRingElem, y::Integer) = add!(x, FlintInt(y))

It also works for types that accept ZZRingElem and Int, but not UInt:

add!(x::ZPolyRingElem, y::ZPolyRingElem) = ...
add!(x::ZPolyRingElem, y::ZRingElem) = ...
add!(x::ZPolyRingElem, y::Int) = ...
# fallback code
add!(x::ZPolyRingElem, y::Integer) = add!(x, FlintInt(y))

Of course I might have missed optimal conversion for some Integer subtype. But then we can fix this by simply adding another FlintInt constructor in a single central place.

Finally, the new type FlintRat plays a similar role for FLINT
functions which also take a QQFieldElem. While RationalUnion is a
counterpart to IntegerUnion.

Note that there are tons more places that could use FlintInt, in particular I'd like to use it to make more generic add! methods for e.g. ZZRingElem and many more (see also this comment thread started by @lgoettgens).

But before I invest more work into this, I'd like to hear what others think about this approach. If we decide against it, no point in wasting more effort on it.


-(a::Integer, b::QQMPolyRingElem) = -(b - a)
-(a::Integer, b::QQMPolyRingElem) = neg!(b - a)
Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, that's an optimization that slipped in here by accident (I was splitting this PR from other work). I'll leave it here (no harm in it, I think) but if we decide against this approach, we'd still want this change in another PR

There are a bunch of FLINT function that come in multiple varieties.
E.g. fmpz_add, fmpz_add_ui, fmpz_add_si all take one fmpz and one
additional argument of type fmpz / UInt / Int, respectively.

So naturally we define different `+` and `add!` methods in Julia using
them. But which method to invoke when adding a `ZZRingElem` and a Julia
integer of a type different from `UInt` and `Int`, such as e.g. `UInt32`
? In that case we currently always convert that into a `ZZRingElem`. But
that's inefficient, it would be be better to convert that value to a
`UInt` or `Int` value and then use the optimized `fmpz_add_ui/_si`
method.

Of course this can be done, but getting it right is tedious, repetitive
and it is easy to overlook a case.

This is where `FlintInt` comes in: this is defined as `Union{Int,
ZZRingElem}`. We then provide constructors which convert any `Integer`
to a `FlintInt` in an optimal way (at least for a bunch of built-in
integer types).

This then can be used to write optimal dispatch for Integer types
like this:

    add!(x::ZRingElem, y::ZRingElem) = ...
    add!(x::ZRingElem, y::Int) = ...
    add!(x::ZRingElem, y::UInt) = ...
    # fallback code
    add!(x::ZRingElem, y::Integer) = add!(x, FlintInt(y))

It also works for types that accept ZZRingElem and Int, but not UInt:

    add!(x::ZPolyRingElem, y::ZPolyRingElem) = ...
    add!(x::ZPolyRingElem, y::ZRingElem) = ...
    add!(x::ZPolyRingElem, y::Int) = ...
    # fallback code
    add!(x::ZPolyRingElem, y::Integer) = add!(x, FlintInt(y))

Of course I might have missed optimal conversion for some `Integer`
subtype. But then we can fix this by simply adding another `FlintInt`
constructor in a single central place.

Finally, the new type `FlintRat` plays a similar role for FLINT
functions which also take a `QQFieldElem`. While `RationalUnion` is a
counterpart to `IntegerUnion`.
Copy link
Collaborator

@lgoettgens lgoettgens left a comment

Choose a reason for hiding this comment

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

I really like this idea (but that was probably expected from the other thread). But let's hear for the other opinions.

src/flint/FlintTypes.jl Outdated Show resolved Hide resolved
Copy link

codecov bot commented Sep 26, 2024

Codecov Report

Attention: Patch coverage is 47.36842% with 10 lines in your changes missing coverage. Please review.

Project coverage is 87.36%. Comparing base (a47d0f6) to head (18923be).
Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
src/flint/fmpz_poly.jl 25.00% 6 Missing ⚠️
src/flint/FlintTypes.jl 50.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1867      +/-   ##
==========================================
+ Coverage   87.00%   87.36%   +0.35%     
==========================================
  Files          98       97       -1     
  Lines       35934    35888      -46     
==========================================
+ Hits        31264    31352      +88     
+ Misses       4670     4536     -134     

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

@thofma
Copy link
Member

thofma commented Sep 26, 2024

I like the idea, but I am not sure that overloading (::Union{...})(...) is the best approach. I have the feeling this makes things more brittle, prone to invalidations etc. How about just adding a small flintification method that can be used instead?

Co-authored-by: Lars Göttgens <[email protected]>
@lgoettgens
Copy link
Collaborator

I like the idea, but I am not sure that overloading (::Union{...})(...) is the best approach. I have the feeling this makes things more brittle, prone to invalidations etc. How about just adding a small flintification method that can be used instead?

having a function flintify(::IntegerUnion) -> FlintInt that does the conversion, or even flint_int(::IntegerUnion) -> FlintInt should work equally well. works for me

@fingolfin
Copy link
Member Author

Yes I agree with @thofma's point. I'll rewrite this to use a function flintify (in case someone has a better idea: changing the name later will of course be trivial)

@fingolfin fingolfin changed the title Introduce FlintInt and FlintRat helpers Introduce flintify helper for "optimal" dispatch on integer and rational inputs Sep 26, 2024
@fingolfin
Copy link
Member Author

Renaming is done now

@fingolfin fingolfin merged commit 7894c21 into Nemocas:master Sep 26, 2024
23 checks passed
@fingolfin fingolfin deleted the mh/FlintInt-FlintRat branch September 26, 2024 21:11
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.

3 participants