//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** @author John Miller * @version 2.0 * @date Sat Mar 22 14:39:30 EDT 2014 * @see LICENSE (MIT style license file). * * @note Base Trait for Random Number Generators */ package scalation package random import scala.math.floor //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `RNG` trait is the base class for all ScalaTion Random Number Generators. * The extending classes must implement a 'gen' method that generates random * real numbers in the range (0, 1). They must also implement an 'igen' methods * to return stream values. * @param stream the random number stream index (0 to N_STREAMS - 1 = 999) */ trait RNG (stream: Int): private val flaw = flawf ("RNG") // flaw function if stream < 0 || stream >= RandomSeeds.N_STREAMS then flaw ("init`", "the stream must be in the range 0 to " + (RandomSeeds.N_STREAMS - 1)) end if //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return the theoretical mean for the random number generator's 'gen' method. */ val mean = 0.5 //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Compute the probability function (pf), i.e., the probability density * function (pdf). * @param z the mass point whose probability density is sought */ def pf (z: Double): Double = is (0.0 <= z && z <= 1.0) //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return the next random number as a real `Double` in the interval (0, 1). */ def gen: Double //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return the next stream value as an integer `Int`. */ def igen: Int end RNG //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `RNGStream` object allows for random selection of streams for applications * where reproducibility of random numbers is not desired. */ object RNGStream: /** Use Java's random number generator to randomly select one of ScalaTion's * random number streams: 0 until `RandomSeeds`.N_STREAMS * "If you use the nullary constructor, new Random(), then 'System.currentTimeMillis' * will be used for the seed, which is good enough for almost all cases." * @see stackoverflow.com/questions/22530702/what-is-seed-in-util-random * @see docs.oracle.com/javase/8/docs/api/index.html */ private val javaRNG = new java.util.Random () //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return a randomly selected random number stream. */ def ranStream: Int = javaRNG.nextInt (RandomSeeds.N_STREAMS) end RNGStream //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `RNGTester` object conducts tests of Random Number Generators: * (1) meansTest: Means Test (including a speed test). * (2) distributionTest: Chi-square Goodness of Fit Test. * (3) distributionTest_KS: K-S Goodness of Fit Test. * (4) correlationTest: Correlation Test. * > runMain scalation.random.RNGTest */ object RNGTester: import scalation.mathstat.{Correlogram, Histogram, VectorD} private val flaw = flawf ("RNG") // flaw function //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Perform a Means Test (average of generated rn's close to mean for distribution). * It also includes a speed test. * @param rn the random number generator to test */ def meansTest (rn: RNG): Unit = val name = rn.getClass.getSimpleName () banner (s"meansTest: $name random number generator") val tries = 500 val reps = 1000000 var sum = 0.0 val means = new VectorD (tries) for i <- 0 until tries do time { for i <- 0 until reps do sum += rn.gen } println (s"gen: sum = $sum") println (s"rn.mean = $rn.mean estimate = ${sum / reps.toDouble}") means(i) = sum sum = 0.0 end for val hg = new Histogram (means, 50, s"Histogram for means as sums of $name") println (s"meansTest: hg = $hg") end meansTest //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Perform a Chi-square Goodness-of-Fit Test. Compare the random number's * histogram (generated by repeatedly calling 'gen') to the probability * function pf (pdf). * @param rn the random number generator to test */ def distributionTest (rn: RNG): Unit = val name = rn.getClass.getSimpleName () banner (s"distributionTest: $name random number generator") val nints = 50 // number of intervals val reps = 1000000 // number of replications val e = reps / nints // expected value: pf (x) val sum = new VectorD (nints) for i <- 0 until reps do val j = floor (rn.gen * nints).toInt // interval number if 0 <= j && j < nints then sum (j) += 1 end for val hg = new Histogram (sum, nints, s"Histogram for distribution of $name", counts = sum) println (s"meansTest: hg = $hg") var chi2 = 0.0 // sum up for Chi-square statistic for i <- sum.indices do val o = sum(i) // observed value: height of histogram chi2 += (o - e)*(o - e) / e print (s"\tsum ($i) = $o : $e ") if i % 5 == 4 then println () end for var n = nints - 1 // degrees of freedom if n < 2 then flaw ("distributionTest", "use more intervals to increase the degrees of freedom") if n > 49 then n = 49 println (s"\nchi2 = $chi2 : chi2(0.95,$n) = ${Quantile.chiSquareInv (0.95, n)}") end distributionTest //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Perform a K-S Goodness-of-Fit Test. Compare the random number's empirical * CDF to the theoretical one. * @param rn the random number generator to test */ def distributionTest_KS (rn: RNG): Unit = val name = rn.getClass.getSimpleName () banner (s"distributionTest_KS: $name random number generator") // FIX - to be coded end distributionTest_KS //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Perform an Auto-correlation Test. Generate a times and examine its correlogram * showing its auto-correlation and partial auto-correlation as functions of lag. * @param rn the random number generator to test */ def correlationTest (rn: RNG): Unit = val name = rn.getClass.getSimpleName () banner (s"correlationTest: $name random number generator") class CoGram (y: VectorD) extends Correlogram (y) val reps = 100000 // number of replications val y = VectorD (for i <- 0 until reps yield rn.gen) val cg = new CoGram (y) cg.makeCorrelogram () cg.plotFunc (cg.acF, s"ACF for $name") cg.plotFunc (cg.pacF, s"PACF for $name") end correlationTest end RNGTester //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `rNGTest` main function conducts three simple tests of the Random Number * Generators. * > runMain scalation.random.rNGTest */ @main def rNGTest (): Unit = /** The Random Number Generators (RNG) to test */ val generators = Array (Random0 (), Random (), Random2 (), Random3 ()) for g <- generators do RNGTester.meansTest (g) RNGTester.distributionTest (g) RNGTester.correlationTest (g) end for end rNGTest