PreviousUpNext

15.4.981  src/lib/src/random.pkg

## random.pkg

# Compiled by:
#     src/lib/std/standard.lib

# This package implements a random number generator using a subtract-with-borrow
# (SWB) generator as described in Marsaglia and Zaman, "A New Class of Random Number
# Generators, " Ann. Applied Prob. 1 (3), 1991, p 462-480.

# The SWB generator is a 31-bit generator with lags 48 and 8. It has period 
# (2^1487 - 2^247)/105 or about 10^445. In general, these generators are
# excellent. However, they act locally like a lagged Fibonacci generator
# and thus have troubles with the birthday test. Thus, we combine this SWB
# generator with the linear congruential generator (48271*a)mod (2^31-1).
#
# Although the interface is fairly abstract, the implementation uses 
# 31-bit Mythryl words. At some point, it might be good to use 32-bit words.            XXX BUGGO FIXME
#
######################################################################
# This is totally obsolete technology; we should
# implement something modern like
#     http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html 
######################################################################



###          "Anyone who considers arithmetic methods
###           of producing random digits is, of course,
###           in a state of sin."
###                            -- Johnny von Neuman



package   random
: (weak)  Random                                # Random                        is from   src/lib/src/random.api
{
    package a   = rw_vector;                    # rw_vector                     is from   src/lib/std/src/rw-vector.pkg
    package lw  = large_unt;                    # large_unt                     is from   src/lib/std/large-unt.pkg
    package w8a = rw_vector_of_one_byte_unts;           # rw_vector_of_one_byte_unts            is from   src/lib/std/src/rw-vector-of-one-byte-unts.pkg
    package w8v = vector_of_one_byte_unts;                      # vector_of_one_byte_unts                       is from   src/lib/std/src/vector-of-one-byte-unts.pkg
    package p   = pack_big_endian_unt1;         # pack_big_endian_unt1          is from   src/lib/std/src/pack-big-endian-unt1.pkg

    my (<<) = (tagged_unt::(<<));
    my (>>) = (tagged_unt::(>>));

    my (&) = tagged_unt::bitwise_and;
    my (|) = tagged_unt::bitwise_or;

    bitwise_xor = tagged_unt::bitwise_xor;

    nbits = 31;                                 # Bits per word 
    my max_word: tagged_unt::Unt = 0ux7FFFFFFF; # largest word 
    my bit30:    tagged_unt::Unt = 0ux40000000;
    my lo30:     tagged_unt::Unt = 0ux3FFFFFFF;

    nnn = 48;
    lag = 8;
    offset = nnn-lag;

    fun error (f, msg)
        =
        lib_base::failure { module=>"Random", fn=>f, msg };

    two2neg30 = 1.0 / ((float(0x8000))*(float(0x8000)));   #  2^-30 

    Random_Number_Generator
        =
        RANDOM_NUMBER_GENERATOR  {
          vals:    a::Rw_Vector( tagged_unt::Unt ),             # Seed rw_vector 
          borrow:  Ref( Bool ),                                 # Last borrow 
          congx:   Ref( tagged_unt::Unt ),                      # Congruential seed 
          index:   Ref( Int )                                   # Index of next available value in vals 
        };


    # We represent state as a string, starting with an initial
    # word acting as an magic cookie (with bit 0 determining the
    # value of borrow), followed by a word containing index and a word
    # containing congx, followed by the seed rw_vector.

    num_words = 3 + nnn;

    my magic:  lw::Unt
            =   0ux72646e64;

    fun to_string (RANDOM_NUMBER_GENERATOR { vals, borrow, congx, index } )
        =
        {   arr   =  w8a::make_rw_vector (4*num_words, 0u0);

            word0 =  if  *borrow    lw::bitwise_or (magic, 0u1);
                                  else   magic;           fi;

            fun fill (src, dst)
                =
                if (src != nnn)
                    p::set (arr, dst, tagged_unt::to_large_unt (a::get (vals, src)));
                    fill (src+1, dst+1);
                fi;
          
            p::set (arr, 0, word0);
            p::set (arr, 1, lw::from_int *index);
            p::set (arr, 2, tagged_unt::to_large_unt *congx);
            fill (0, 3);
            byte::bytes_to_string (w8a::to_vector arr);
        };

    fun from_string s
        =
        {   bytes = byte::string_to_bytes s;

            if   (w8v::length bytes != 4 * num_words)
                 error ("from_string", "invalid state string");
            fi;

            word0 = p::get_vec (bytes, 0);

            if   (lw::bitwise_and (word0, 0uxFFFFFFFE) != magic)
                 error ("from_string", "invalid state string");
            fi;

            fun get_vec i
                =
                p::get_vec (bytes, i);

            borrow = REF (lw::bitwise_and (word0, 0u1) == 0u1);
            index  = REF (lw::to_int (get_vec 1));
            congx  = REF (tagged_unt::from_large_unt (get_vec 2));

            arr =  a::make_rw_vector (nnn, 0u0:  tagged_unt::Unt);

            fun fill (src, dst)
                =
                if (dst != nnn)
                    a::set (arr, dst, tagged_unt::from_large_unt (get_vec src));
                    fill (src+1, dst+1);
                fi;

            fill (3, 0);

            RANDOM_NUMBER_GENERATOR
                { vals => arr,
                  index, 
                  congx, 
                  borrow
                };
          };

    # linear congruential generator:
    # multiplication by 48271 mod (2^31 - 1) 

    my a:  tagged_unt::Unt = 0u48271;
    my m:  tagged_unt::Unt = 0u2147483647;

    q = m / a;
    r = m % a;

    fun lcg seed
        =
        {   left  = a * (seed % q);
            right = r * (seed / q);
          
            if   (left > right)   left - right;
            else                    (m - right) + left;  fi;
        };

    # Fill seed rw_vector using subtract-with-borrow generator:
    #  x[n] = x[n-lag] - x[n-nnn] - borrow
    # Sets index to 1 and returns 0th value.

    fun fill (RANDOM_NUMBER_GENERATOR { vals, index, congx, borrow } )
        =
        {
            fun minus (x, y, FALSE) =>  (x - y,        y >  x);
                minus (x, y, TRUE ) =>  (x - y - 0u1,  y >= x);
            end;

            fun update (ix, iy, b)
                =
                {   my (z, b')
                        =
                        minus ( a::get (vals, ix),
                                a::get (vals, iy),
                                b
                              );

                    a::set (vals, iy, z); b';
                };

            fun fillup (i, b)
                =
                if   (i == lag)   b;
                else              fillup (i+1, update (i+offset, i, b));
                fi;

            fun fillup' (i, b)
                =
                if   (i == nnn)   b;
                else              fillup'(i+1, update (i-lag, i, b));
                fi;

            borrow := fillup' (lag, fillup (0,*borrow));
            index  := 1;

            a::get (vals, 0);
        };

    # Create initial seed rw_vector and state of generator.
    # Fills the seed rw_vector one bit at a time by taking the leading 
    # bit of the xor of a shift register and a congruential sequence. 
    # The congruential generator is (c*48271) mod (2^31 - 1).
    # The shift register generator is c (I + L18)(I + R13).
    # The same congruential generator continues to be used as a 
    # mixing generator with the SWB generator.

    fun make_random_number_generator (congy, shrgx)
        =
        {   fun mki (i, c, s)
                =
                {   c'  = lcg c;

                    s'  = bitwise_xor (s, s << 0u18);
                    s'' = bitwise_xor (s', s' >> 0u13);

                    i' = (lo30 & (i >> 0u1)) | (bit30 & bitwise_xor (c', s''));

                    (i', c', s'');
                };

            fun iterate (0, v) =>  v;
                iterate (n, v) =>  iterate (n - 1, mki v);
            end;

            fun make_seed (congx, shrgx)
                =
                iterate (nbits, (0u0, congx, shrgx));


            fun genseed (0, seeds, congx, _)
                    =>
                    (seeds, congx);

                genseed (n, seeds, congx, shrgx)
                    =>
                    {   my (seed, congx', shrgx') = make_seed (congx, shrgx);
                        genseed (n - 1, seed ! seeds, congx', shrgx');
                    };
            end;

            congx = ((tagged_unt::from_int congy & max_word) << 0u1)+0u1;

            my (seeds, congx)
                =
                genseed (nnn,[], congx, tagged_unt::from_int shrgx);

            RANDOM_NUMBER_GENERATOR { vals => a::from_list seeds, 
                  index => REF 0, 
                  congx => REF congx, 
                  borrow => REF FALSE };
          };


    # Get next random number. The tweak function combines
    # the number from the SWB generator with a number from
    # the linear congruential generator.
    #
    fun rand_word (r as RANDOM_NUMBER_GENERATOR { vals, index, congx, ... } )
        =
        {   idx =  *index;

            fun tweak i
                =
                {   c =  lcg *congx;

                    congx := c;
                    bitwise_xor (i, c);
                };
         
           if   (idx == nnn)   tweak (fill r);
           else                tweak (a::get (vals, idx))
                               then
                                   index := idx+1;
           fi;
        };

    fun int             state =  tagged_unt::to_int_x (rand_word state);
    fun nonnegative_int state =  tagged_unt::to_int_x (rand_word state & lo30);

    fun float01 state
        =
        (float(nonnegative_int state) + float(nonnegative_int state) * two2neg30) * two2neg30;

    fun range (i, j)
        = 
        if   (j < i )
             error ("random_range", "hi < lo");
        else
             {   rrr = two2neg30 * float(j - i + 1);
                 \\ s = i + trunc (rrr * float(nonnegative_int s));
             }
             except
                 _ =  {   ri  =  float(i);
                          rrr = (float(j))-ri+1.0;
                
                          \\ s =  trunc (ri + rrr*(float01 s));
                      };
        fi;


    fun bool state
        =
        (int state) > 0;

}; #  random 



Comments and suggestions to: bugs@mythryl.org

PreviousUpNext