-
Notifications
You must be signed in to change notification settings - Fork 67
follow the Random API for Poly and Integers via RandomExtensions.make #648
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
Conversation
src/generic/Poly.jl
Outdated
RandomExtensions.maketype(S::PolyRing, dr::UnitRange{Int}, _...) = elem_type(S) | ||
|
||
# define rand for make(S, deg_range, v) | ||
function rand(rng::AbstractRNG, sp::Random.SamplerTrivial{<:RandomExtensions.Make3{<:RingElement,<:PolyRing,UnitRange{Int}}}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make3
is an alias for Make
objects containing 3 elements; it's first type parameter is the type of produced objects, and the 3 subsequent type parameters correspond to the types of the 3 stored objects.
src/generic/Poly.jl
Outdated
@@ -2743,16 +2743,38 @@ end | |||
# | |||
############################################################################### | |||
|
|||
function rand(rng::AbstractRNG, S::AbstractAlgebra.PolyRing, deg_range::UnitRange{Int}, v...) | |||
RandomExtensions.maketype(S::PolyRing, dr::UnitRange{Int}, _...) = elem_type(S) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maketype
takes the same arguments as make
, and must return the type of produced random elements. We could probably define a catch-all method in AA, like maketype(S::AbstractAlgebra.Set, x...) = elem_type(S)
.
This final line is something I can personally live with, if it solves some other problem you'd like solved:
So if that works, I'm ok with this. |
And I'm personally ok with adding a dependency on RandomExtensions, so long as it is not doing anything hacky that will need updating with every new version of Julia. I know others don't necessarily agree with adding dependencies. But I'll let them chime in with their opinion on that. |
Note that the I also like the "flattened" API for convenience, although the nested calls provide some clarity in reflecting the nesting structure: it's easy to forget what each argument do in
Nice! I don't think that anything hacky will cause any problem. My current medium term plan (maybe within 6 months) is to extract out the core feature related to |
My main concern was not |
Note also that I didn't remove the existing |
Good point, although a minimal amount of knowledge is necessary to know which arguments to pass. As a side note, I have a very experimental work in progress to allow the user to not have to give these parameters, for testing purposes. |
Of course that would also be useful from my personal point of view. The better our testing, the better everything is. But I imagine this particular feature is not a high priority item. |
Thanks @rfourquet, I like the interface. I am not too big of a fan of external dependencies. On the other hand |
fine with me - waiting for the corresponding pull request for Nemo, Singular, (Polymake,) Hecke and Oscar... |
3d6116f
to
29d2b03
Compare
Given the support, I will then add the few remaining needed
I will gladly do! For |
On Wed, Sep 23, 2020 at 01:54:25AM -0700, Rafael Fourquet wrote:
Given the support, I will then add the few remaining needed `make` and `rand` methods in this PR and merge.
> waiting for the corresponding pull request for Nemo, Singular, (Polymake,) Hecke and Oscar...
I will gladly do! For `Singular`, `Polymake` and `GAP` I didn't find any defined `rand` methods so there is nothing to do on this front. In Oscar.jl, there are very few `rand` methods, but they rely on GAP's own non-exposed RNG, so a bit more work has to be done, but this currently doesn't need `make` methods, as there is already only one argument to `rand`.
Singular has them - but they are dodgy: rand(poly over F_p) - what
should this be?
…
--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#648 (comment)
|
Ok, so I looked only for Julia code in Singular.jl, where I didn't find any |
On Wed, Sep 23, 2020 at 05:38:59AM -0700, Rafael Fourquet wrote:
> Singular has them
Ok, so I looked only for Julia code in Singular.jl, where I didn't find any `rand` method defined. I will look eventually into writing them, but I was planning to first update those which exist already (so mainly AA, Nemo, Hecke for now).
It might not be in Singular.jl - there exists some randomness in
singular - but maybe not exported
… --
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#648 (comment)
|
So this change (and others I haven't yet pushed to this branch) breaks Nemo tests, until Nemo is updated with similar changes. I don't know if this is "considered" as breaking (as no documented behavior is changed AFAICT), so I don't know if this should increment the patch or minor version number. What I would do is to bump the patch version, and depend on that right away in Nemo with the new changes, so that both master branches remain compatible (and probably similarly with Hecke). If the minor version must be bumped, maybe you prefer accumulating more breaking changes before a new version... let me know how to proceed. |
If we know it will break packages upstream, we should bump the minor version. Otherwise, with just a patch version bump, one might end up with a combination of packages which we know will not work. I will mark this as breaking. |
Yes, good point! A question on style: in this PR there is currently heave signatures like
In the corresponding Nemo PR I prepared, I imported
Which I find easier on the eyes, but I'm biased as I know where As a side note, I will write a macro eventually to simplify this boilerplate, so this becomes something like
|
29d2b03
to
a975f26
Compare
I personally find the shorter signatures better. Currently we do not have some kind of dev docs, otherwise it would be a good idea to record some of the design decisions. Maybe we can find a place somewhere in the source files and put it as a comment there. |
Ok, I will update accordingly.
Yes good idea. For this particular case, it would be good that |
On Fri, Sep 25, 2020 at 05:39:56AM -0700, Rafael Fourquet wrote:
> I personally find the shorter signatures better.
Ok, I will update accordingly.
> Currently we do not have some kind of dev docs, otherwise it would be a good idea to record some of the design decisions. Maybe we can find a place somewhere in the source files and put it as a comment there.
Yes good idea. For this particular case, it would be good that
`RandomExtensions` provides better docs explaining the rationale of
the `make` methods, and how this can be used. But yes, I should
probably write something in AA docs about that.
.. but only as a second step, after it has been updated.
…
--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#648 (comment)
|
Let's try to merge soon. Is there something that needs to be added @rfourquet? |
Ah yes, let me import some names to make signatures shorter... |
Before I push the update: shall I bump the AA version to 0.11.0 here? |
Yes, thanks. |
Am I doing something wrong? julia> rand(Make(ZZ, 1:3), 3)
ERROR: UndefVarError: Make not defined
Stacktrace:
[1] top-level scope at REPL[5]:1
julia> AbstractAlgebra.Make
ERROR: UndefVarError: Make not defined
Stacktrace:
[1] getproperty(::Module, ::Symbol) at ./Base.jl:26
[2] top-level scope at REPL[6]:1 |
So the function to use as a user is |
What is available out of the box is the (experimental) julia> rand(ZZ => 1:3, 3)
3-element Array{BigInt,1}:
1
2
2 |
Sorry for my misunderstanding. I thought that if I want to repeatedly call |
Ah, sorry, |
EDIT: oh ok, I see things are clear now for you! Anyway, I keep this answer I had written:
No, So julia> r = big(1):big(1000)
1:1000
julia> @btime rand($rng, $r)
279.647 ns (6 allocations: 96 bytes)
112
julia> s = @btime Sampler(MersenneTwister, $r)
97.814 ns (2 allocations: 48 bytes)
Random.SamplerBigInt(1, 999, 1, 1, 0x00000000000003ff)
julia> @btime rand($rng, $s);
179.578 ns (4 allocations: 48 bytes) So if you are going to call So for AA, I didn't try yet to find optimizations related to creating a custom |
Thanks again for clarification, greatly appreciated. |
As discussed in the Jitsi meeting last Friday. I made an implementation instead of just proposing an API because it's very difficult to know whether the idea of an API will work in practice, and the amount of implementation work was reasonable here.
In this case, "Following the Random API" means that one object is enough to encode all the necessary information for the various
rand
methods to know how to draw random objects (numbers, polynomials...) together with an RNG.For example
1:3
orBool
are such objects (they mean draw numbers from the{1, 2, 3}
set or from the{false, true}
set respectively).One counter-example is how we draw integers in AA: our API is e.g.
rand(ZZ, 1:3)
: two objects (ZZ
and1:3
) are necessary in order to convey torand
all the information.One advantage of following the "Random API" is composability. For example, you can create arrays (
rand(1:3, 2, 3)
) or randomize already existing arrays (rand!([0, 0, 0], 1:3)
).The obvious solution when more than one object are necessary is to pack them into one
struct
(not into aTuple
, asrand(::Tuple)
already has a meaning). This is for example what distributions from theDistributions
package do, e.g. withNormal(0.0, 1.0)
.But it can be very tedious to define custom
struct
s for all the variousrand
methods you want to define (both for the implementer, and for the user who has to remember all the ad-hoc names). For example, we would haverand(RandIntegers(ZZ, 1:3))
whereRandIntegers
is such a custom struct with two fields.One solution to this problem is to use a custom generic
struct
which behaves likeTuple
, call itMake
:Make(a, b, c)
contains as its unique field the tuple(a, b, c)
, and represents a "distribution" (in an extended meaning), containing all the information needed byrand
. Then, the implementer doesn't have to define newstruct
s for eachrand
method, and the user just has to remember one name:Make
. For example we would have:Now, when elements of a tuple
t
are types, thent
's type doesn't know which type it contains (e.g.typeof(1, Bool) == Tuple{Int,DataType}
), so this leads to type instabilities. Working around this makes the implementation ofMake
slightly less straight-forward; one such implementation is in theRandomExtensions.jl
package, which exports themake
function (instead ofMake
) as the interface for creating "distributions" (such that the example above works with this PR just by replacingMake
bymake
).As seen in the above example, recursivity is handled via nested
Make/make
calls. To mirror the current "flattened" API (e.g.rand(R, 0:5, -10:10)
), this PR also defines for convenience a flattenedmake
API (e.g.make(R, 0:5, -10:10)
).RandomExtensions
also defines an experimental convenience API using pairs, such thatrand(a => b)
is interpreted asrand(make(a, b))
andrand(a => (b, c...))
is interpreted asrand(make(a, b, c...))
. So you can doI have thought a lot about this problem in general (independently of AA/Oscar), and didn't find better than the
make
framework (started about 3 years ago) fromRandomExtensions
, which I consider to be quite solid at this point (allowing a non-type object as the first argument ofmake
is a new feature though, and was not yet extensively tested).cc @fieker @wbhart