Skip to content

Conversation

@rfourquet
Copy link
Member

We had two sets of constructors:

  1. user-facing ones, which mimic show
    (e.g. MersenneTwister(seed), or MersenneTwister(1, (0, 1002, 0, 1))),
  2. internal ones:
    • MersenneTwister(seed, state, vals, ...)
    • MersenneTwister(seed, state)

Internal ones were not practical to use, so they are replaced by a single MersenneTwister(undef) constructor which prepares an uninitialized instance, which can then be initialized by seed!, copy!, etc.

This commit also makes const some internal fields, and replaces Vector with Memory for one of them.

We had two sets of constructors:
1) user-facing ones, which mimic `show`
   (e.g. `MersenneTwister(seed)`, or `MersenneTwister(1, (0, 1002, 0, 1))`),
2) internal ones:
   - `MersenneTwister(seed, state, vals, ...)`
   - `MersenneTwister(seed, state)`

Internal ones were not practical to use, so they are replaced by a single
`MersenneTwister(undef)` constructor which prepares an uninitialized instance,
which can then be initialized by `seed!`, `copy!`, etc.

This commit also makes `const` some internal fields, and replaces `Vector`
with `Memory` for one of them.
@rfourquet rfourquet added the randomness Random number generation and the Random stdlib label Nov 20, 2025
@assert dsfmt_get_min_array_size() <= MT_CACHE_F

mutable struct MersenneTwister <: AbstractRNG
seed::Any
Copy link
Member

Choose a reason for hiding this comment

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

does a MersenneTwister really need to store it's seed?

Copy link
Member

Choose a reason for hiding this comment

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

Yes looks like this can be deleted.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's there to recreate the state of an instance from few values, so that what show outputs corresponds to a real constructor. E.g

julia> MersenneTwister("a seed")
MersenneTwister("a seed")

But I was thinking to perhaps normalize seeds into an UInt128 value and store that instead of the original seed. The example above would then look like

julia> MersenneTwister("a seed")
MersenneTwister(0xb4802685c420b29be64de36a1f90815f)

julia> MersenneTwister(0xb4802685c420b29be64de36a1f90815f)
MersenneTwister(0xb4802685c420b29be64de36a1f90815f)

This would involve an additional round with SeedHasher, but I'm precisely preparing another PR to make that much faster.

Copy link
Member

Choose a reason for hiding this comment

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

what about just having a HashedSeed type?

Copy link
Member Author

Choose a reason for hiding this comment

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

The user seed is removed in #60204, without an additional round with SeedHasher, by just storing two UInt128 values instead of one. Printing is uglier, but who cares.

@Seelengrab
Copy link
Contributor

I don't think exposing an explicit undef constructor is a good idea. This introduces a source of nondeterminism that the current (internal) API doesn't have. I think it would be better to have the internal API use a different name, e.g. _MersenneTwister, where either the old methids or any new constructors can live.

@rfourquet
Copy link
Member Author

I don't think exposing an explicit undef constructor is a good idea

That's actually something I've thought to bring as an experimental API in v1.14, but I'm fine postponing its introduction to a future PR if there is push-back here.

@Seelengrab
Copy link
Contributor

What I'm saying is that I don't think having MersenneTwister(undef) is a good idea in any version. An RNG can be viewed as a resource, and having the possibility in the API for creating an uninitialized resource here is dangerous, as someone is bound to forget to seed! it. What benefit does having this give to a user, that they can't achieve with existing functionality?

@rfourquet
Copy link
Member Author

As you noted, there is already a internal MT constructor which leaves an instance in an uninitialized state (though admitedly it's harder to call because there are many arguments). It's also possible to initialize Xoshiro badly like in Xoshiro(0, 0, 0, 0), which we actually do in RNGs.jl to initialize Random.GLOBAL_RNG.

What benefit does having this give to a user, that they can't achieve with existing functionality?

Let's leave discussing that for the future PR I mentioned. But in short, there are certain low-level use cases for which it can be convenient to get an unitialized instance, which can be initialized later (similarly to Vector{Int}(undef, n), so I thought re-using undef for RNGs is fit). But of course it's just an optimization (like for arrays).

@rfourquet
Copy link
Member Author

I changed MersenneTwister(undef) to _MersenneTwister(undef), to make this less controversial.

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

Labels

randomness Random number generation and the Random stdlib

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants