Learning Objectives

  • To be able to generate random numbers using the Kotlin Simulation Library (KSL)
  • To understand how to control random number streams within the KSL
  • To be able to generate random variates using the KSL
  • To understand how to use the KSL for basic probability computations

Random Number Generator

  • L’Ecuyer, P., R. Simard, and W. D. Kelton. 2002. “An Object-Oriented Random Number Package with Many Long Streams and Substreams.” Operations Research 50: 1073–75.
  • Based on the combination of two multiple recursive generators resulting in a period of approximately \(3.1 \times 10^{57}\)

\[ \begin{aligned} R_{1,i} & = (1,403,580 R_{1,i-2} - 810,728 R_{1,i-3})\bmod (2^{32}-209)\\ R_{2,i} & = (527,612R_{2,i-1} - 1,370,589 R_{2,i-3})\bmod (2^{32}-22,853)\\ Y_i & = (R_{1,i}-R_{2,i})\bmod(2^{32}-209)\\ U_i & = \frac{Y_i}{2^{32}-209} \end{aligned} \]

Random Package

Random Number Stream Interfaces

Example

  • To create random number streams, use a RNStreamProvider and ask it for a stream.
  • Then use the streams, randU01() function to get pseudo-random numbers uniformly distributed between 0 and 1.
fun main() {
    // make a provider for creating streams
    val p1 = RNStreamProvider()
    // get the first stream from the provider
    val p1s1 = p1.rnStream(streamNum = 1)
    // make another provider, the providers are identical
    val p2 = RNStreamProvider()
    // Thus, the first streams returned are identical
    val p2s1 = p2.rnStream(streamNum = 1)
    print(String.format("%3s %15s %15s %n", "n", "p1s1", "p2s1"))
    for (i in 1..5) {
        print(String.format("%3d %15f %15f %n", i, p1s1.randU01(), p2s1.randU01()))
    }
}

Example Output

  • Both of the streams p1s1 and p2s2 produce the same sequence of random numbers because they were created from two different instances of RNStreamProvider that have the same initial starting state (seeds).
  n            p1s1            p2s1 
  1        0.127011        0.127011 
  2        0.318528        0.318528 
  3        0.309186        0.309186 
  4        0.825847        0.825847 
  5        0.221630        0.221630 

RNStreamProvider Interface

The RNStreamProvider has a well-define set of functions to get streams and to control the behavior of streams that it produced.

  • nextRNStream() - returns the next random number stream associated with the provider. Each call to nextRNStream() makes a new stream in the sequence of streams.
  • lastRNStreamNumber() - returns the number of the stream that was last made. This indicates how many streams have been made. If \(0\) is returned, then no streams have been made by the provider.
  • rnStream(k : Int) - returns the \(k^{th}\) stream. If \(k\) is greater than lastRNStreamNumber() then lastRNStreamNumber() is advanced according to the additional number of streams by creating any intermediate streams.
  • streamNumber(RNStreamIfc stream) - returns the stream number of the instance of a stream.
  • advanceStreamMechanism(int n) - advances the underlying stream mechanism by the specified number of streams, without actually creating the streams.
  • resetRNStreamSequence() - Causes the random number stream provider to act as if has never created any streams.

Stream Control Via RNStreamControlIfc Interface

The RNStreamProvider creates and instance of a stream that implements the RNStreamIfc interface, which has a well-defined set of functions for moving and manipulating the stream.

  • resetStartStream() - positions the random number stream at the beginning of its sequence This is the same location in the stream as assigned when the random number stream was created and initialized.
  • resetStartSubstream() - resets the position of the random number stream to the start of the current substream. If the random number stream has advanced into the substream, then this method resets to the beginning of the substream.
  • advanceToNextSubStream() - positions the random number stream at the beginning of its next substream. This method move through the current substream and positions the stream at the beginning of the next substream.
  • antithetic indicates to the stream to start producing antithetic variates. If the option is true, the stream should start producing antithetic variates with the next call to randU01(). If the option is false, the stream should stop producing antithetic variates.

The KSLRandom Object

The KSLRandom object has a default RNStreamProvider that can be used to get stream.

  • nextRNStream() - calls the underlying default RNStreamProvider to create a new random number stream
  • rnStream(int k) - returns the \(k^{th}\) stream from the default RNStreamProvider
  • defaultRNStream() - calls the underlying default RNStreamProvider for its default stream
fun main() {
    val s1 = KSLRandom.defaultRNStream()
    println("Default stream is stream 1")
    println("Generate 3 random numbers")
    for (i in 1..3) {
        println("u = " + s1.randU01())
    }
}

Basic Stream Control

fun main() {
    val s1 = KSLRandom.defaultRNStream()
    s1.advanceToNextSubStream()
    println("Advance to next sub-stream and get some more random numbers")
    for (i in 1..3) {
        println("u = " + s1.randU01())
    }
    println("Notice that they are different from the first 3.")
    s1.resetStartStream()
    println("Reset the stream to the beginning of its sequence")
    for (i in 1..3) {
        println("u = " + s1.randU01())
    }
    println("Notice that they are the same as the first 3.")
    println("Get another random number stream")
    val s2 = KSLRandom.nextRNStream()
    println("2nd stream")
    for (i in 1..3) {
        println("u = " + s2.randU01())
    }
    println("Notice that they are different from the first 3.")
}

Common Random Numbers by Cloning the Stream

  • Common random numbers (CRN) is a Monte Carlo method that has different experiments utilize the same random numbers.
  • Use multiple instances of RNStreamProvider or clone the stream to implement CRN.
  • This code clones the existing stream, which creates a new stream that is a duplicate of the current stream.
fun main() {
    // get the default stream
    val s = KSLRandom.defaultRNStream()
    // make a clone of the stream
    val clone = s.crnInstance()
    print(String.format("%3s %15s %15s %n", "n", "U", "U again"))
    for (i in 1..3) {
        print(String.format("%3d %15f %15f %n", i, s.randU01(), clone.randU01()))
    }
}

Common Random Numbers by Cloning the Stream Output

  • The clone “U again” repeats the sequence of numbers.
  • This approach uses two different objects to repeat the sequence.
 n               U         U again 
  1        0.127011        0.127011 
  2        0.318528        0.318528 
  3        0.309186        0.309186 

Common Random Numbers by Resetting the Stream

  • An alternative method is to just use the resetStartStream() method of the stream to reset the stream to the desired location in its sequence and then reproduce the random numbers.
fun main() {
    val s = KSLRandom.defaultRNStream()
    // generate regular
    print(String.format("%3s %15s %n", "n", "U"))
    for (i in 1..3) {
        val u = s.randU01()
        print(String.format("%3d %15f %n", i, u))
    }
    // reset the stream and generate again
    s.resetStartStream()
    println()
    print(String.format("%3s %15s %n", "n", "U again"))
    for (i in 1..3) {
        val u = s.randU01()
        print(String.format("%3d %15f %n", i, u))
    }
}

CRN by Resetting the Stream Output

  • By resetting the stream to its starting position, we can repeat the sequence of numbers.
  • This approach uses one object to repeat the sequence.
  n               U 
  1        0.127011 
  2        0.318528 
  3        0.309186 

  n         U again 
  1        0.127011 
  2        0.318528 
  3        0.309186 

Antithetic Streams by Cloning the Stream

  • If a pseudo-random number is called \(U\) then its antithetic value is \(1-U\).
  • Use the stream to create an antithetic instance via the antitheticInstance() method.
fun main() {
    // get the default stream
    val s = KSLRandom.defaultRNStream()
    // make its antithetic version
    val ans = s.antitheticInstance()
    print(String.format("%3s %15s %15s %15s %n", "n", "U", "1-U", "sum"))
    for (i in 1..5) {
        val u = s.randU01()
        val au = ans.randU01()
        print(String.format("%3d %15f %15f %15f %n", i, u, au, u + au))
    }
}

Antithetic Streams by Cloning the Stream Output

  • Clearly \(U + (1-U) = 1\)
  • Note that if \(U\) is low, then \(1-U\) is high. This is negative correlation.
  n               U             1-U             sum 
  1        0.127011        0.872989        1.000000 
  2        0.318528        0.681472        1.000000 
  3        0.309186        0.690814        1.000000 
  4        0.825847        0.174153        1.000000 
  5        0.221630        0.778370        1.000000 

Anthithetic Streams by Resetting the Stream

  • An alternate method that does not require the creation of another stream involves using the resetStartStream() method and the antithetic property of the current stream.
fun main() {
    val s = KSLRandom.defaultRNStream()
    s.resetStartStream()
    // generate regular
    print(String.format("%3s %15s %n", "n", "U"))
    for (i in 1..5) {
        val u = s.randU01()
        System.out.printf("%3d %15f %n", i, u)
    }
    // generate antithetic
    s.resetStartStream()
    s.antithetic = true
    println()
    print(String.format("%3s %15s %n", "n", "1-U"))
    for (i in 1..5) {
        val u = s.randU01()
        print(String.format("%3d %15f %n", i, u))
    }
}

Anthithetic Streams by Resetting the Stream Output

  • The resulting \(U\) and \(1-U\) are the same as in the cloned stream example
  • Your preference for cloning or resetting depends on the specifics of the required application.
  • The KSL supports both approaches.
  n               U 
  1        0.127011 
  2        0.318528 
  3        0.309186 
  4        0.825847 
  5        0.221630 

  n             1-U 
  1        0.872989 
  2        0.681472 
  3        0.690814 
  4        0.174153 
  5        0.778370 
⌂ Index