Skip to content

Commit

Permalink
A pseudorandom number generator. (#56)
Browse files Browse the repository at this point in the history
Introduces a new utility class `cgsuite.util.Random` that provides a pseudorandom number source.
  • Loading branch information
aaron-siegel authored Jan 12, 2024
1 parent 827d8c4 commit 8bb0916
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ system class Collection
*/
external def Concat(elements as Collection);

/** The number of elements of this `Collection` that match the
* specified predicate. `predicate` must be a [[Boolean]]-valued
* function.
*/
external def Count(predicate as Function);

/** `true` if there exists an element in this collection
* that satisfies the given predicate.
* `predicate` must be a [[Boolean]]-valued function.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*${cgsuite.banner}*/

/** A pseudorandom number generator.
*
* To construct an instance of `Random`, specify an integer seed:
*
* \display{random := Random(1474)}
*
* Then use the [[#NextInteger]] method to generate output. This example produces integers `n` with `0 <= n < 100`:
*
* \display{random.NextInteger(100)}
*
* \display{[random.NextInteger(100) for n from 1 to 20]}
*/
mutable system class Random(
/** The seed for this `Random`. The seed must satisfy:
*
* `-2^63 <= seed < 2^63`
*/
seed as Integer
)

/** The next `Integer` in this stream of pseudorandom numbers. The output `n` of `NextInteger(upperBound)` will always
* satisfy
*
* `0 <= n < upperBound`
*
* and will be (pseudo-)uniformly distributed within that range.
*/
external def NextInteger(upperBound as Integer);

override def ToOutput := "Random(" + seed.ToOutput + ")";

end
9 changes: 7 additions & 2 deletions lib/core/src/main/scala/org/cgsuite/core/Integer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,17 @@ trait Integer extends DyadicRationalNumber with GeneralizedOrdinal {

def bigIntValue: BigInt
override def intValue = {
if (bigIntValue.bigInteger.bitLength() <= 32)
if (bigIntValue.bigInteger.bitLength() < 32)
bigIntValue.intValue
else
throw OverflowException("Overflow.")
}
def longValue = bigIntValue.longValue
def longValue = {
if (bigIntValue.bigInteger.bitLength() < 64)
bigIntValue.longValue
else
throw OverflowException("Overflow.")
}
def floatValue = bigIntValue.floatValue
def doubleValue = bigIntValue.doubleValue
def byteValue = bigIntValue.byteValue
Expand Down
10 changes: 5 additions & 5 deletions lib/core/src/main/scala/org/cgsuite/lang/SpecialMethods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.cgsuite.lang
import org.cgsuite.core._
import org.cgsuite.core.impartial.Spawning
import org.cgsuite.exception.{EvalException, InvalidArgumentException, NotNumberException}
import org.cgsuite.lang.CgscriptClass.SafeCast
import org.cgsuite.lang.CgscriptClass.{SafeCast, internalize}
import org.cgsuite.output.{ScatterPlotOutput, StyledTextOutput}
import org.cgsuite.util.{Coordinates, Symmetry, Table}

Expand Down Expand Up @@ -72,6 +72,10 @@ object SpecialMethods {

"cgsuite.lang.Collection.Adjoin" -> { (collection: Iterable[_], obj: Any) => collection ++ Iterable(obj) },
"cgsuite.lang.Collection.Concat" -> { (collection: Iterable[_], that: Iterable[_]) => collection ++ that },
"cgsuite.lang.Collection.Count" -> { (collection: Iterable[_], fn: Function) =>
validateArity(fn, 1)
internalize(collection.count { x => fn.call(Array(x)).castAs[java.lang.Boolean] }.asInstanceOf[java.lang.Integer])
},
"cgsuite.lang.Collection.Exists" -> { (collection: Iterable[_], fn: Function) =>
validateArity(fn, 1)
collection.exists { x => fn.call(Array(x)).castAs[java.lang.Boolean] }
Expand Down Expand Up @@ -168,10 +172,6 @@ object SpecialMethods {

private val specialMethods2: Map[String, (_, _) => Any] = Map(

/*
"cgsuite.util.Graph.FromList" -> { (_: ClassObject, args: (IndexedSeq[Any], IndexedSeq[Any])) =>
Graph(args._1.map { _.asInstanceOf[IndexedSeq[Integer]] }, Option(args._2)) },
*/
"cgsuite.lang.List.Sublist" -> { (list: IndexedSeq[_], range: (Integer, Integer)) =>
list.slice(range._1.intValue - 1, range._2.intValue)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ private[lang] object SystemClassRegistry {

"cgsuite.util.Strip" -> classOf[Strip],
"cgsuite.util.Grid" -> classOf[Grid],
"cgsuite.util.Random" -> classOf[Random],
"cgsuite.util.Symmetry" -> classOf[Symmetry],
"cgsuite.util.DirectedGraph" -> classOf[DirectedGraph[_, _]],
"cgsuite.util.Edge" -> classOf[Graph.Edge[_]],
Expand Down
23 changes: 23 additions & 0 deletions lib/core/src/main/scala/org/cgsuite/util/Random.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.cgsuite.util

import org.cgsuite.core.Integer
import org.cgsuite.core.Values.one
import org.cgsuite.exception.EvalException

class Random(val seed: Integer) {

private val random = new scala.util.Random(seed.longValue)

def nextInteger(upperBound: Integer): Integer = {
if (upperBound < one) {
throw EvalException("Upper bound for `NextInteger` must be >= 1.")
}
val bits = (upperBound - one).lb.intValue + 1
var result: Integer = Integer(BigInt(bits, random))
while (result >= upperBound) {
result = Integer(BigInt(bits, random))
}
result
}

}
12 changes: 12 additions & 0 deletions lib/core/src/test/scala/org/cgsuite/lang/CgsuiteUtilTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,16 @@ class CgsuiteUtilTest extends CgscriptSpec {

}

"cgsuite.util.Random" should "implement methods correctly" in {

executeTests(Table(
header,
("Construct a Random", "random := Random(1474)", "Random(1474)"),
("Random.NextInteger", "[random.NextInteger(100) for n from 1 to 20]",
"[64,92,17,25,63,58,88,49,0,30,85,46,58,24,72,31,98,61,61,69]"),
("Random overflow", "Random(2^63)", "!!Overflow.")
))

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ object CollectionTestCase {
"[]", "[]",
adjoin = "[19]",
concat = "[10,*2]",
count = "0",
exists = "false",
flattened = "[]",
forall = "true",
Expand All @@ -27,6 +28,7 @@ object CollectionTestCase {
"[0,1,3,9,7,5]", "[0,1,3,9,7,5]",
adjoin = "[0,1,3,9,7,5,19]",
concat = "[0,1,3,9,7,5,10,*2]",
count = "2",
exists = "true",
flattened = "[0,1,3,9,7,5]",
forall = "false",
Expand All @@ -46,6 +48,7 @@ object CollectionTestCase {
"""["Winning", "Ways", "Mathematical", "Plays"]""", """["Winning","Ways","Mathematical","Plays"]""",
adjoin = """["Winning","Ways","Mathematical","Plays",19]""",
concat = """["Winning","Ways","Mathematical","Plays",10,*2]""",
count = "!!No operation `>=` for arguments of types `cgsuite.lang.String`, `game.Integer`",
exists = "false",
flattened = """["Winning","Ways","Mathematical","Plays"]""",
forall = "false",
Expand All @@ -70,6 +73,7 @@ case class CollectionTestCase(
xOut: String,
adjoin: String,
concat: String,
count: String,
exists: String,
flattened: String,
forall: String,
Expand All @@ -89,6 +93,7 @@ case class CollectionTestCase(
(x, xOut),
(s"($x).Adjoin(19)", adjoin),
(s"($x).Concat([10,*2])", concat),
(s"($x).Count(x -> x >= 2 and x < 7)", count),
(s"($x).Exists(x -> x == 7)", exists),
(s"($x).Flattened", flattened),
(s"($x).ForAll(x -> x == 7)", forall),
Expand Down

0 comments on commit 8bb0916

Please sign in to comment.