Files
mercury/library/random.m
Julien Fischer 65566350b5 Add the system random number generator interface.
This is work-in-progress, currently only the C# and Java backends
are supported. Support for the C backends will be added separately.

library/random.system_rng.m:
    A new submodule containing the system RNG.

library/random.m:
library/library.m:
    Include the new submodule.

library/MODULES_UNDOC:
    Do not generate documentation for the new submodule.
2021-01-29 16:30:18 +11:00

952 lines
30 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1994-1998,2001-2006, 2011 The University of Melbourne.
% Copyright (C) 2015-2016, 2018-2019 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%---------------------------------------------------------------------------%
%
% File: random.m
% Main author: Mark Brown
%
% This module provides interfaces to several random number generators,
% implementations of which can be found in the submodules.
%
% The interfaces can be used in three styles:
%
% - In the "ground" style, an instance of the random/1 typeclass is
% passed through the code using 'in' and 'out' modes. This can be used
% to generate random numbers, and since the value is ground it can also
% easily be stored in larger data structures. The major drawback is that
% generators in this style tend to be either fast or of good quality,
% but not both.
%
% - In the "unique" style, the urandom/2 typeclass is used instead. Each
% instance consists of a "params" type which is passed into the code
% using an 'in' mode, and a "state" type which is passed through the
% code using modes 'di' and 'uo'. The uniqueness allows destructive
% update, which means that these generators can be both fast and good.
%
% - A generator can be attached to the I/O state. In this case, the
% interface is the same as the unique style, with 'io' being used as
% the unique state.
%
% Each generator defined in the submodules is natively one of the first
% two styles. Adaptors are defined below for converting between these,
% or from either of these to the third style.
%
%
% Example, ground style:
%
% main(!IO) :-
% R0 = sfc16.init,
% roll(R0, R1, !IO),
% roll(R1, _, !IO).
%
% :- pred roll(R::in, R::out, io::di, io::uo) is det <= random(R).
%
% roll(!R, !IO) :-
% uniform_int_in_range(1, 6, N, !R),
% io.format("You rolled a %d\n", [i(N)], !IO).
%
%
% Example, unique style:
%
% main(!IO) :-
% sfc64.init(P, S0),
% roll(P, S0, S1, !IO),
% roll(P, S1, _, !IO).
%
% :- pred roll(P::in, S::di, S::uo, io::di, io::uo) is det <= urandom(P, S).
%
% roll(P, !S, !IO) :-
% uniform_int_in_range(P, 1, 6, N, !S),
% io.format("You rolled a %d\n", [i(N)], !IO).
%
%
% Example, attached to I/O state:
%
% main(!IO) :-
% % Using a ground generator.
% R = sfc16.init,
% make_io_random(R, M1, !IO),
% roll(M1, !IO),
% roll(M1, !IO),
%
% % Using a unique generator.
% sfc64.init(P, S),
% make_io_urandom(P, S, M2, !IO),
% roll(M2, !IO),
% roll(M2, !IO).
%
% :- pred roll(M::in, io::di, io::uo) is det <= urandom(M, io).
%
% roll(M, !IO) :-
% uniform_int_in_range(M, 1, 6, N, !IO),
% io.format("You rolled a %d\n", [i(N)], !IO).
%
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- module random.
:- interface.
:- include_module sfc16.
:- include_module sfc32.
:- include_module sfc64.
:- include_module system_rng.
:- import_module array.
:- import_module io.
:- import_module list.
%---------------------------------------------------------------------------%
% Interface to random number generators.
%
:- typeclass random(R) where [
% Generate a uniformly distributed pseudo-random unsigned integer
% of 8, 16, 32 or 64 bits, respectively.
%
pred generate_uint8(uint8::out, R::in, R::out) is det,
pred generate_uint16(uint16::out, R::in, R::out) is det,
pred generate_uint32(uint32::out, R::in, R::out) is det,
pred generate_uint64(uint64::out, R::in, R::out) is det
].
% uniform_int_in_range(Start, Range, N, !R)
%
% Generate a pseudo-random integer that is uniformly distributed
% in the range Start to (Start + Range - 1), inclusive.
%
% Throws an exception if Range < 1 or Range > uint32_max.
%
:- pred uniform_int_in_range(int::in, int::in, int::out, R::in, R::out)
is det <= random(R).
% uniform_uint_in_range(Start, Range, N, !R)
%
% Generate a pseudo-random unsigned integer that is uniformly
% distributed in the range Start to (Start + Range - 1), inclusive.
%
% Throws an exception if Range < 1 or Range > uint32_max.
%
:- pred uniform_uint_in_range(uint::in, uint::in, uint::out, R::in, R::out)
is det <= random(R).
% uniform_float_in_range(Start, Range, N, !R)
%
% Generate a pseudo-random float that is uniformly distributed
% in the interval [Start, Start + Range).
%
:- pred uniform_float_in_range(float::in, float::in, float::out, R::in, R::out)
is det <= random(R).
% uniform_float_around_mid(Mid, Delta, N, !R)
%
% Generate a pseudo-random float that is uniformly distributed
% in the interval (Mid - Delta, Mid + Delta).
%
:- pred uniform_float_around_mid(float::in, float::in, float::out,
R::in, R::out) is det <= random(R).
% uniform_float_in_01(N, !R)
%
% Generate a pseudo-random float that is uniformly distributed
% in the interval [0.0, 1.0).
%
:- pred uniform_float_in_01(float::out, R::in, R::out) is det <= random(R).
% normal_floats(M, SD, U, V, !R)
%
% Generate two pseudo-random floats from a normal (i.e., Gaussian)
% distribution with mean M and standard deviation SD, using the
% Box-Muller method.
%
% We generate two at a time for efficiency; they are independent of
% each other.
%
:- pred normal_floats(float::in, float::in, float::out, float::out,
R::in, R::out) is det <= random(R).
% normal_floats(U, V, !R)
%
% Generate two pseudo-random floats from a normal (i.e., Gaussian)
% distribution with mean 0.0 and standard deviation 1.0, using the
% Nox-Muller method.
%
% We generate two at a time for efficiency; they are independent of
% each other.
%
:- pred normal_floats(float::out, float::out, R::in, R::out) is det
<= random(R).
% Generate a random permutation of a list.
%
:- pred shuffle_list(list(T)::in, list(T)::out, R::in, R::out) is det
<= random(R).
% Generate a random permutation of an array.
%
:- pred shuffle_array(array(T)::array_di, array(T)::array_uo, R::in, R::out)
is det <= random(R).
%---------------------------------------------------------------------------%
% Interface to unique random number generators. Callers need to
% ensure they preserve the uniqueness of the random state, and in
% turn instances can use destructive update on it.
%
:- typeclass urandom(P, S) <= (P -> S) where [
% Generate a uniformly distributed pseudo-random unsigned integer
% of 8, 16, 32 or 64 bits, respectively.
%
pred generate_uint8(P::in, uint8::out, S::di, S::uo) is det,
pred generate_uint16(P::in, uint16::out, S::di, S::uo) is det,
pred generate_uint32(P::in, uint32::out, S::di, S::uo) is det,
pred generate_uint64(P::in, uint64::out, S::di, S::uo) is det
].
:- typeclass urandom_dup(S) where [
% urandom_dup(!S, !:Sdup)
%
% Create a duplicate random state that will generate the same
% sequence of integers.
%
pred urandom_dup(S::di, S::uo, S::uo) is det
].
% uniform_int_in_range(P, Start, Range, N, !S)
%
% Generate a pseudo-random integer that is uniformly distributed
% in the range Start to (Start + Range - 1), inclusive.
%
% Throws an exception if Range < 1 or Range > uint32_max.
%
:- pred uniform_int_in_range(P::in, int::in, int::in, int::out, S::di, S::uo)
is det <= urandom(P, S).
% uniform_uint_in_range(P, Start, Range, N, !S)
%
% Generate a pseudo-random unsigned integer that is uniformly
% distributed in the range Start to (Start + Range - 1), inclusive.
%
% Throws an exception if Range < 1 or Range > uint32_max.
%
:- pred uniform_uint_in_range(P::in, uint::in, uint::in, uint::out,
S::di, S::uo) is det <= urandom(P, S).
% uniform_float_in_range(P, Start, Range, N, !S)
%
% Generate a pseudo-random float that is uniformly distributed
% in the interval [Start, Start + Range).
%
:- pred uniform_float_in_range(P::in, float::in, float::in, float::out,
S::di, S::uo) is det <= urandom(P, S).
% uniform_float_around_mid(P, Mid, Delta, N, !S)
%
% Generate a pseudo-random float that is uniformly distributed
% in the interval (Mid - Delta, Mid + Delta).
%
:- pred uniform_float_around_mid(P::in, float::in, float::in, float::out,
S::di, S::uo) is det <= urandom(P, S).
% uniform_float_in_01(P, N, !S)
%
% Generate a pseudo-random float that is uniformly distributed
% in the interval [0.0, 1.0).
%
:- pred uniform_float_in_01(P::in, float::out, S::di, S::uo) is det
<= urandom(P, S).
% normal_floats(P, M, S, U, V, !S)
%
% Generate two pseudo-random floats from a normal (i.e., Gaussian)
% distribution with mean M and standard deviation S, using the
% Box-Muller method.
%
% We generate two at a time for efficiency; they are independent of
% each other.
%
:- pred normal_floats(P::in, float::in, float::in, float::out, float::out,
S::di, S::uo) is det <= urandom(P, S).
% normal_floats(P, U, V, !S)
%
% Generate two pseudo-random floats from a normal (i.e., Gaussian)
% distribution with mean 0.0 and standard deviation 1.0, using the
% Box-Muller method.
%
% We generate two at a time for efficiency; they are independent of
% each other.
%
:- pred normal_floats(P::in, float::out, float::out, S::di, S::uo) is det
<= urandom(P, S).
% Generate a random permutation of a list.
%
:- pred shuffle_list(P::in, list(T)::in, list(T)::out, S::di, S::uo) is det
<= urandom(P, S).
% Generate a random permutation of an array.
%
:- pred shuffle_array(P::in, array(T)::array_di, array(T)::array_uo,
S::di, S::uo) is det <= urandom(P, S).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
% Convert any instance of random/1 into an instance of urandom/2.
% This creates additional overhead in the form of additional
% typeclass method calls.
%
:- type urandom_params(R).
:- type urandom_state(R).
:- instance urandom(urandom_params(R), urandom_state(R)) <= random(R).
:- instance urandom_dup(urandom_state(R)) <= random(R).
:- pred make_urandom(R::in, urandom_params(R)::out, urandom_state(R)::uo)
is det.
%---------------------------------------------------------------------------%
% Convert any instance of urandom/2 and urandom_dup/1 into an
% instance of random/1. This duplicates the state every time a
% random number is generated, hence may use significantly more
% memory than if the unique version were used directly.
%
:- type shared_random(P, S).
:- instance random(shared_random(P, S)) <= (urandom(P, S), urandom_dup(S)).
:- func make_shared_random(P::in, S::di) = (shared_random(P, S)::out) is det.
%---------------------------------------------------------------------------%
% Convert any instance of random/1 into an instance of urandom/2
% where the state is the I/O state.
%
:- type io_random(R).
:- instance urandom(io_random(R), io) <= random(R).
:- pred make_io_random(R::in, io_random(R)::out, io::di, io::uo) is det
<= random(R).
%---------------------------------------------------------------------------%
% Convert any instance of urandom/2 into an instance of urandom/2
% where the state is the I/O state.
%
:- type io_urandom(P, S).
:- instance urandom(io_urandom(P, S), io) <= urandom(P, S).
:- pred make_io_urandom(P::in, S::di, io_urandom(P, S)::out, io::di, io::uo)
is det <= urandom(P, S).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% Interface to the older random number generator. This is now deprecated.
%
% Define a set of random number generator predicates. This implementation
% uses a threaded random-number supply. The supply can be used in a
% non-unique way, which means that each thread returns the same list of
% random numbers. However, this may not be desired so in the interests
% of safety it is also declared with (backtrackable) unique modes.
%
% The coefficients used in the implementation were taken from Numerical
% Recipes in C (Press et al), and are originally due to Knuth. These
% coefficients are described as producing a "Quick and Dirty" random number
% generator, which generates the numbers very quickly but not necessarily
% with a high degree of quality. As with all random number generators,
% the user is advised to consider carefully whether this generator meets
% their requirements in terms of "randomness". For applications which have
% special needs (e.g. cryptographic key generation), a generator such as
% this is unlikely to be suitable.
%
% Note that random number generators of this type have several known
% pitfalls which the user may need to avoid:
%
% 1) The high bits tend to be more random than the low bits. If
% you wish to generate a random integer within a given range, you
% should something like 'div' to reduce the random numbers to the
% required range rather than something like 'mod' (or just use
% random.random/5).
%
% 2) Similarly, you should not try to break a random number up into
% components. Instead, you should generate each number with a
% separate call to this module.
%
% 3) There can be sequential correlation between successive calls,
% so you shouldn't try to generate tuples of random numbers, for
% example, by generating each component of the tuple in sequential
% order. If you do, it is likely that the resulting sequence will
% not cover the full range of possible tuples.
%
%---------------------------------------------------------------------------%
% The type `supply' represents a supply of random numbers.
%
:- type supply.
% init(Seed, RS).
%
% Creates a supply of random numbers RS using the specified Seed.
%
% This predicate has been declared obsolete because all of the
% interface from here on is deprecated. All code using this part
% of the interface will need to be updated.
%
:- pragma obsolete(init/2).
:- pred init(int::in, supply::uo) is det.
% random(Num, !RS).
%
% Extracts a number Num in the range 0 .. RandMax from the random number
% supply !RS.
%
:- pred random(int, supply, supply).
:- mode random(out, in, out) is det.
:- mode random(out, mdi, muo) is det.
% random(Low, Range, Num, !RS).
%
% Extracts a number Num in the range Low .. (Low + Range - 1) from the
% random number supply !RS. For best results, the value of Range should be
% no greater than about 100.
%
:- pred random(int, int, int, supply, supply).
:- mode random(in, in, out, in, out) is det.
:- mode random(in, in, out, mdi, muo) is det.
% randmax(RandMax, !RS).
%
% Binds RandMax to the maximum random number that can be returned from the
% random number supply !RS, the state of the supply is unchanged.
%
:- pred randmax(int, supply, supply).
:- mode randmax(out, in, out) is det.
:- mode randmax(out, mdi, muo) is det.
% randcount(RandCount, !RS).
%
% Binds RandCount to the number of distinct random numbers that can be
% returned from the random number supply !RS. The state of the supply is
% unchanged. This will be one more than the number returned by randmax/3.
%
:- pred randcount(int, supply, supply).
:- mode randcount(out, in, out) is det.
:- mode randcount(out, mdi, muo) is det.
% permutation(List0, List, !RS).
%
% Binds List to a random permutation of List0.
%
:- pred permutation(list(T), list(T), supply, supply).
:- mode permutation(in, out, in, out) is det.
:- mode permutation(in, out, mdi, muo) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
% Everything after the first `:- implementation' does not appear
% in the Mercury Library Reference Manual.
:- interface.
%---------------------------------------------------------------------------%
:- implementation.
:- import_module float.
:- import_module int.
:- import_module math.
:- import_module mutvar.
:- import_module uint.
:- import_module uint32.
%---------------------------------------------------------------------------%
uniform_int_in_range(Start, Range0, N, !R) :-
Range = uint32.det_from_int(Range0),
Max = uint32.max_uint32,
generate_uint32(N0, !R),
N1 = N0 // (Max // Range),
( if N1 < Range then
N = Start + uint32.cast_to_int(N1)
else
uniform_int_in_range(Start, Range0, N, !R)
).
uniform_uint_in_range(Start, Range0, N, !R) :-
Range = uint32.cast_from_uint(Range0),
Max = uint32.max_uint32,
generate_uint32(N0, !R),
N1 = N0 // (Max // Range),
( if N1 < Range then
N = Start + uint32.cast_to_uint(N1)
else
uniform_uint_in_range(Start, Range0, N, !R)
).
uniform_float_in_range(Start, Range, N, !R) :-
uniform_float_in_01(N0, !R),
N = Start + (N0 * Range).
uniform_float_around_mid(Mid, Delta, N, !R) :-
uniform_float_in_01(N0, !R),
( if N0 = 0.0 then
uniform_float_around_mid(Mid, Delta, N, !R)
else
N = Mid + Delta * (2.0 * N0 - 1.0)
).
uniform_float_in_01(N, !R) :-
generate_uint64(N0, !R),
D = 18_446_744_073_709_551_616.0, % 2^64
N = float.cast_from_uint64(N0) / D.
normal_floats(M, SD, U, V, !R) :-
normal_floats(U0, V0, !R),
U = M + SD * U0,
V = M + SD * V0.
normal_floats(U, V, !R) :-
uniform_float_in_range(-1.0, 2.0, X, !R),
uniform_float_in_range(-1.0, 2.0, Y, !R),
( if uniform_to_normal(X, Y, U0, V0) then
U = U0,
V = V0
else
normal_floats(U, V, !R)
).
shuffle_list(L0, L, !R) :-
A0 = array(L0),
shuffle_array(A0, A, !R),
L = array.to_list(A).
shuffle_array(A0, A, !R) :-
Lo = array.min(A0),
Hi = array.max(A0),
Sz = array.size(A0),
shuffle_2(Lo, Lo, Hi, Sz, A0, A, !R).
:- pred shuffle_2(int::in, int::in, int::in, int::in,
array(T)::array_di, array(T)::array_uo, R::in, R::out) is det
<= random(R).
shuffle_2(I, Lo, Hi, Sz, !A, !R) :-
( if I > Hi then
true
else
uniform_int_in_range(Lo, Sz, J, !R),
array.unsafe_swap(I, J, !A),
shuffle_2(I + 1, Lo, Hi, Sz, !A, !R)
).
%---------------------------------------------------------------------------%
uniform_int_in_range(P, Start, Range0, N, !S) :-
Range = uint32.det_from_int(Range0),
Max = uint32.max_uint32,
generate_uint32(P, N0, !S),
N1 = N0 // (Max // Range),
( if N1 < Range then
N = Start + uint32.cast_to_int(N1)
else
uniform_int_in_range(P, Start, Range0, N, !S)
).
uniform_uint_in_range(P, Start, Range0, N, !S) :-
Range = uint32.cast_from_uint(Range0),
Max = uint32.max_uint32,
generate_uint32(P, N0, !S),
N1 = N0 // (Max // Range),
( if N1 < Range then
N = Start + uint32.cast_to_uint(N1)
else
uniform_uint_in_range(P, Start, Range0, N, !S)
).
uniform_float_in_range(P, Start, Range, N, !S) :-
uniform_float_in_01(P, N0, !S),
N = Start + (N0 * Range).
uniform_float_around_mid(P, Mid, Delta, N, !S) :-
uniform_float_in_01(P, N0, !S),
( if N0 = 0.0 then
uniform_float_around_mid(P, Mid, Delta, N, !S)
else
N = Mid + Delta * (2.0 * N0 - 1.0)
).
uniform_float_in_01(P, N, !S) :-
generate_uint64(P, N0, !S),
D = 18_446_744_073_709_551_616.0, % 2^64
N = float.cast_from_uint64(N0) / D.
normal_floats(P, M, SD, U, V, !S) :-
normal_floats(P, U0, V0, !S),
U = M + SD * U0,
V = M + SD * V0.
normal_floats(P, U, V, !S) :-
uniform_float_in_range(P, -1.0, 2.0, X, !S),
uniform_float_in_range(P, -1.0, 2.0, Y, !S),
( if uniform_to_normal(X, Y, U0, V0) then
U = U0,
V = V0
else
normal_floats(P, U, V, !S)
).
shuffle_list(P, L0, L, !S) :-
A0 = array(L0),
shuffle_array(P, A0, A, !S),
L = array.to_list(A).
shuffle_array(P, A0, A, !S) :-
Lo = array.min(A0),
Hi = array.max(A0),
Sz = array.size(A0),
shuffle_2(P, Lo, Lo, Hi, Sz, A0, A, !S).
:- pred shuffle_2(P::in, int::in, int::in, int::in, int::in,
array(T)::array_di, array(T)::array_uo, S::di, S::uo) is det
<= urandom(P, S).
shuffle_2(P, I, Lo, Hi, Sz, !A, !S) :-
( if I > Hi then
true
else
uniform_int_in_range(P, Lo, Sz, J, !S),
array.unsafe_swap(I, J, !A),
shuffle_2(P, I + 1, Lo, Hi, Sz, !A, !S)
).
%---------------------------------------------------------------------------%
:- pred uniform_to_normal(float::in, float::in, float::out, float::out)
is semidet.
uniform_to_normal(X, Y, U, V) :-
S = X * X + Y * Y,
S > 0.0,
S < 1.0,
Fac = math.sqrt(-2.0 * math.ln(S) / S),
U = X * Fac,
V = Y * Fac.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- type urandom_params(R)
---> urandom_params.
:- type urandom_state(R)
---> urandom_state(R).
:- instance urandom(urandom_params(R), urandom_state(R)) <= random(R) where [
( generate_uint8(_, N, S0, S) :-
S0 = urandom_state(R0),
generate_uint8(N, R0, R),
S = unsafe_promise_unique(urandom_state(R))
),
( generate_uint16(_, N, S0, S) :-
S0 = urandom_state(R0),
generate_uint16(N, R0, R),
S = unsafe_promise_unique(urandom_state(R))
),
( generate_uint32(_, N, S0, S) :-
S0 = urandom_state(R0),
generate_uint32(N, R0, R),
S = unsafe_promise_unique(urandom_state(R))
),
( generate_uint64(_, N, S0, S) :-
S0 = urandom_state(R0),
generate_uint64(N, R0, R),
S = unsafe_promise_unique(urandom_state(R))
)
].
:- instance urandom_dup(urandom_state(R)) <= random(R) where [
( urandom_dup(S, S1, S2) :-
S1 = unsafe_promise_unique(S),
S2 = unsafe_promise_unique(S)
)
].
make_urandom(R, P, S) :-
P = urandom_params,
S = unsafe_promise_unique(urandom_state(R)).
%---------------------------------------------------------------------------%
:- type shared_random(P, S)
---> shared_random(
shared_random_params :: P,
shared_random_state :: S
).
:- instance random(shared_random(P, S)) <= (urandom(P, S), urandom_dup(S))
where [
( generate_uint8(N, R0, R) :-
R0 = shared_random(P, S0),
S1 = unsafe_promise_unique(S0),
urandom_dup(S1, _, S2),
generate_uint8(P, N, S2, S),
R = shared_random(P, S)
),
( generate_uint16(N, R0, R) :-
R0 = shared_random(P, S0),
S1 = unsafe_promise_unique(S0),
urandom_dup(S1, _, S2),
generate_uint16(P, N, S2, S),
R = shared_random(P, S)
),
( generate_uint32(N, R0, R) :-
R0 = shared_random(P, S0),
S1 = unsafe_promise_unique(S0),
urandom_dup(S1, _, S2),
generate_uint32(P, N, S2, S),
R = shared_random(P, S)
),
( generate_uint64(N, R0, R) :-
R0 = shared_random(P, S0),
S1 = unsafe_promise_unique(S0),
urandom_dup(S1, _, S2),
generate_uint64(P, N, S2, S),
R = shared_random(P, S)
)
].
make_shared_random(P, S) = shared_random(P, S).
%---------------------------------------------------------------------------%
:- type io_random(R)
---> io_random(mutvar(R)).
:- instance urandom(io_random(R), io) <= random(R) where [
pred(generate_uint8/4) is io_random_gen_uint8,
pred(generate_uint16/4) is io_random_gen_uint16,
pred(generate_uint32/4) is io_random_gen_uint32,
pred(generate_uint64/4) is io_random_gen_uint64
].
:- pred io_random_gen_uint8(io_random(R)::in, uint8::out, io::di, io::uo)
is det <= random(R).
:- pragma promise_pure(io_random_gen_uint8/4).
io_random_gen_uint8(io_random(V), N, !IO) :-
impure get_mutvar(V, R0),
generate_uint8(N, R0, R),
impure set_mutvar(V, R).
:- pred io_random_gen_uint16(io_random(R)::in, uint16::out, io::di, io::uo)
is det <= random(R).
:- pragma promise_pure(io_random_gen_uint16/4).
io_random_gen_uint16(io_random(V), N, !IO) :-
impure get_mutvar(V, R0),
generate_uint16(N, R0, R),
impure set_mutvar(V, R).
:- pred io_random_gen_uint32(io_random(R)::in, uint32::out, io::di, io::uo)
is det <= random(R).
:- pragma promise_pure(io_random_gen_uint32/4).
io_random_gen_uint32(io_random(V), N, !IO) :-
impure get_mutvar(V, R0),
generate_uint32(N, R0, R),
impure set_mutvar(V, R).
:- pred io_random_gen_uint64(io_random(R)::in, uint64::out, io::di, io::uo)
is det <= random(R).
:- pragma promise_pure(io_random_gen_uint64/4).
io_random_gen_uint64(io_random(V), N, !IO) :-
impure get_mutvar(V, R0),
generate_uint64(N, R0, R),
impure set_mutvar(V, R).
:- pragma promise_pure(make_io_random/4).
make_io_random(R, Pio, !IO) :-
impure new_mutvar(R, V),
Pio = io_random(V).
%---------------------------------------------------------------------------%
:- type io_urandom(P, S)
---> io_urandom(P, mutvar(S)).
:- instance urandom(io_urandom(P, S), io) <= urandom(P, S) where [
pred(generate_uint8/4) is io_urandom_gen_uint8,
pred(generate_uint16/4) is io_urandom_gen_uint16,
pred(generate_uint32/4) is io_urandom_gen_uint32,
pred(generate_uint64/4) is io_urandom_gen_uint64
].
:- pred io_urandom_gen_uint8(io_urandom(P, S)::in, uint8::out, io::di, io::uo)
is det <= urandom(P, S).
:- pragma promise_pure(io_urandom_gen_uint8/4).
io_urandom_gen_uint8(io_urandom(P, V), N, !IO) :-
impure get_mutvar(V, S0),
S1 = unsafe_promise_unique(S0),
generate_uint8(P, N, S1, S),
impure set_mutvar(V, S).
:- pred io_urandom_gen_uint16(io_urandom(P, S)::in, uint16::out,
io::di, io::uo) is det <= urandom(P, S).
:- pragma promise_pure(io_urandom_gen_uint16/4).
io_urandom_gen_uint16(io_urandom(P, V), N, !IO) :-
impure get_mutvar(V, S0),
S1 = unsafe_promise_unique(S0),
generate_uint16(P, N, S1, S),
impure set_mutvar(V, S).
:- pred io_urandom_gen_uint32(io_urandom(P, S)::in, uint32::out,
io::di, io::uo) is det <= urandom(P, S).
:- pragma promise_pure(io_urandom_gen_uint32/4).
io_urandom_gen_uint32(io_urandom(P, V), N, !IO) :-
impure get_mutvar(V, S0),
S1 = unsafe_promise_unique(S0),
generate_uint32(P, N, S1, S),
impure set_mutvar(V, S).
:- pred io_urandom_gen_uint64(io_urandom(P, S)::in, uint64::out,
io::di, io::uo) is det <= urandom(P, S).
:- pragma promise_pure(io_urandom_gen_uint64/4).
io_urandom_gen_uint64(io_urandom(P, V), N, !IO) :-
impure get_mutvar(V, S0),
S1 = unsafe_promise_unique(S0),
generate_uint64(P, N, S1, S),
impure set_mutvar(V, S).
:- pragma promise_pure(make_io_urandom/5).
make_io_urandom(P, S, Pio, !IO) :-
impure new_mutvar(S, V),
Pio = io_urandom(P, V).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- type supply
---> rs(int). % I(j)
:- pred params(int::out, int::out, int::out) is det. % a, c, m
params(9301, 49297, 233280).
init(I0, rs(RS)) :-
copy(I0, RS).
random(I, rs(RS0), rs(RS)) :-
RS0 = I0,
random.params(A, C, M),
I = ((I0 * A) + C) mod M,
copy(I, RS).
% We could make this more robust by checking whether the range is
% less than a certain threshold, and using a more sophisticated
% algorithm if the threshold is exceeded. But that would defeat
% the purpose of having a "quick and dirty" random number generator,
% so we don't do that.
random(Low, Range, Num, !RandomSupply) :-
random(R, !RandomSupply),
randcount(M, !RandomSupply),
% With our current set of parameters and a reasonable choice of Range,
% the following should never overflow.
Num = Low + (Range * R) // M.
randmax(M1, RS, RS) :-
params(_A, _C, M),
M1 = M - 1.
randcount(M, RS, RS) :-
params(_A, _C, M).
%---------------------------------------------------------------------------%
% The random permutation is implemented via a "sampling without
% replacement" method. In init_record, we build up an array in which
% every integer in the range 0 .. Length - 1 is mapped to the
% corresponding element in the list. The sampling stage
% iterates from Length - 1 down to 0. The invariant being
% maintained is that at iteration I, the elements in the image of
% the part of the map indexed by 0 .. I-1 are the elements that have
% not been selected yet. At each iteration, perform_sampling generates
% a random number Index in the range 0 .. I-1, adds the element that
% Index is mapped to, Next, to the permutation, and then ensures that
% Next is not generated again by swapping it with the image of I-1.
permutation(List0, List, !RS) :-
Samples = array(List0),
Len = array.size(Samples),
perform_sampling(Len, Samples, [], List, !RS).
:- pred perform_sampling(int, array(T), list(T), list(T),
random.supply, random.supply).
:- mode perform_sampling(in, array_di, in, out, in, out) is det.
:- mode perform_sampling(in, array_di, in, out, mdi, muo) is det.
perform_sampling(I, !.Record, !Order, !RS) :-
( if I =< 0 then
true
else
I1 = I - 1,
random.random(0, I, Index, !RS),
array.lookup(!.Record, Index, Next),
array.lookup(!.Record, I1, MaxImage),
!:Order = [Next | !.Order],
array.set(Index, MaxImage, !Record),
array.set(I1, Next, !Record),
perform_sampling(I1, !.Record, !Order, !RS)
).
%---------------------------------------------------------------------------%
% The following predicate was just for test purposes.
% It should not be used by user programs.
%
:- pred test(int::in, int::in, list(int)::out, int::out) is det.
:- pragma consider_used(test/4).
:- pragma obsolete(test/4).
test(Seed, N, Nums, Max) :-
init(Seed, RS),
randmax(Max, RS, RS1),
test_2(N, Nums, RS1, _RS2).
:- pred test_2(int, list(int), supply, supply).
:- mode test_2(in, out, in, out) is det.
test_2(N, Is, !RS) :-
( if N > 0 then
N1 = N - 1,
random(I, !RS),
test_2(N1, Is0, !RS),
Is = [I | Is0]
else
Is = []
).
%---------------------------------------------------------------------------%