RVar

An instance of RVar represents a computation that, when executed, results in a value with randomness applied. RVar is one of the most important structures in CIlib and is therefore discussed first in order to understand how the data structure works.

RVar has a monad instance and therefore allows a for a large amount of composition, but more importantly allows for the tracking of randomness within the RVar computation. This tracking is of the utmost importance within computational intelligence algorithms, as randomness needs to be controlled in a manner that facilitates repetition. In other words, even if a computation uses randomness, given the same inputs, the same results are expected even with randomness applied.

Due to the monadic nature of the data structure, the data structure may be transformed by functions such as map, flatMap, etc

There are several predefined combinators that allow the user to use and create RVar computations. These include functions for randomness applied to primitive types (such and Int and Double) to more complex types that build on the primitives, or even for user defined types.

The simplest would be to look at some examples of RVar usage. It is quite common to request several random numbers. RVar provides several functions, with ints and doubles being the most common for random variable creation:

     | val ints = RVar.ints(5)
ints: cilib.RVar[List[Int]] = cilib.RVar$$anon$2@e8cd5ee

scala> val doubles = RVar.doubles(5)
doubles: cilib.RVar[List[Double]] = cilib.RVar$$anon$2@1d6758c8

Both functions result in a RVar that, when provided with a pseudo-random number generator (PRNG), will result in a list of values.

The user if free to define a PRNG for themselves, but CIlib provides a default PRNG that is suitable for scientific work. The CMWC generator may be initialized by either providing a seed value for the pseudo-random number stream, or it may be taken from the current time of the computer. It is always recommended to record the seed value, so that others may reproduce results, especially if the results are to be published.

Let’s create a RNG instance using both methods:

scala> val rng = RNG.init(1234L)
rng: cilib.RNG = cilib.CMWC@65f28afb

scala> val fromTimeYOLO = RNG.fromTime
fromTimeYOLO: cilib.RNG = cilib.CMWC@6fba65f4

Now, let’s run both doubles and ints with the generator:

scala> val r1 = ints.run(rng)
r1: (cilib.RNG, List[Int]) = (cilib.CMWC@6108a1c3,List(-2012280037, -456312394, -1608573853, -1720473833, 1662253751))

scala> doubles.run(rng)
res1: (cilib.RNG, List[Double]) = (cilib.CMWC@3a6440e6,List(0.5314795508050395, 0.6254747152242208, 0.3870236094802634, 0.3590652564475848, 0.1350043437170152))

scala> val r2 = ints.run(rng)
r2: (cilib.RNG, List[Int]) = (cilib.CMWC@18c0fc35,List(-2012280037, -456312394, -1608573853, -1720473833, 1662253751))

scala> r1._2 == r2._2
res2: Boolean = true

The result is a tuple of the state of the PRNG after being used in the computation, together with the result of the computation itself. The important point to note is that running the computation again, with the same PRNG, that is the original state of the PRNG will result in the same obtained results. Unlike the normal PRNG within the JVM platform, obtaining some random value from the source does not implicitly mutate the PRNG. In order to keep selecting from the PRNG stream, the next state of the PRNG should be passed into subsequent computations, when needed:

scala> val (rng2, x) = ints.run(rng)
rng2: cilib.RNG = cilib.CMWC@359f02e6
x: List[Int] = List(-2012280037, -456312394, -1608573853, -1720473833, 1662253751)

scala> val (rng3, y) = ints.run(rng2)
rng3: cilib.RNG = cilib.CMWC@7ed27a03
y: List[Int] = List(-973239118, 1542173555, 1984558752, 579839250, 609517835)

scala> x != y
res3: Boolean = true

This manual state passing for the PRNG is very cumbersome and as a result, the monad instance of RVar provides this exact functionality to the user, thereby preventing accidental errors due to incorrect usage of PRNG state. Furthermore, the monad instance for RVar allows for cleaner syntax through the use of a for-comprehension as provided by Scala:

scala> val composition = for {
     |   a <- RVar.next[Int] // Get a single Int
     |   b <- RVar.next[Double] // Get a single Double, using the next state of the PRNG
     |   c <- RVar.next[Boolean] // Get a Boolean, again passing the PRNG state
     | } yield if (c) a*b else b
composition: cilib.RVar[Double] = cilib.RVar$$anon$2@2b856ae9

scala> composition.run(rng)
res4: (cilib.RNG, Double) = (cilib.CMWC@73e166e5,-1.798488339436242E9)

From this definition of how randomness is managed, we can derive several useful algorithms which operate within the RVar computation. Please refer to the scaladoc for more combinators, but some of the more commonly used are illustrated below:

scala> val sampleList = List(6,4,5,2,1,3)
sampleList: List[Int] = List(6, 4, 5, 2, 1, 3)

scala> RVar.shuffle(sampleList).run(rng)
res5: (cilib.RNG, List[Int]) = (cilib.CMWC@55d4de7a,List(4, 3, 5, 1, 2, 6))

scala> RVar.sample(3, sampleList).run.run(rng)
res6: (cilib.RNG, Option[List[Int]]) = (cilib.CMWC@3a1405b9,Some(List(5, 3, 4)))

Building on RVar, we can easily define probability distributions from which, randomness may be sampled. The provided distributions, where standard distributions are also defined, include:

  • Uniform
  • Gaussian / Normal
  • Cauchy
  • Gamma
  • Exponential
  • etc

The interface for the distributions is simply a resulting RVar

scala> // Use a derived function from monad to repeat an action 'n' times
     | Dist.stdNormal.replicateM(5).run(rng)
res8: (cilib.RNG, List[Double]) = (cilib.CMWC@1733d248,List(0.12471034611540575, 0.10241216207344515, -0.49485286860096944, -2.6592051237450325, 0.7016898748604742))