Universe

Suiji (随机 in Chinese, /suíjī/, meaning random) is a high efficient random number generator in Typst. Partial algorithm is inherited from GSL and most APIs are similar to NumPy Random Generator. It provides pure function implementation and does not rely on any global state variable, resulting in better performance and independency.

Features

  • All functions are immutable, which means results of random are completely deterministic.
  • Core random engine chooses “Maximally equidistributed combined Tausworthe generator”, which has higher quality and efficiency compared with other algorithms like LCG, MT19937, etc.
  • Generate random integers or floats from various distribution.
  • Randomly shuffle an array of objects.
  • Randomly sample from an array of objects.

Examples

The example below uses suiji and cetz packages to create a trajectory of a random walk.

#import "@preview/suiji:0.2.0": *
#import "@preview/cetz:0.2.2"

#set page(width: auto, height: auto, margin: 0.5cm)

#cetz.canvas(length: 5pt, {
  import cetz.draw: *

  let n = 2000
  let (x, y) = (0, 0)
  let (x-new, y-new) = (0, 0)
  let rng = gen-rng(42)
  let v = ()

  for i in range(n) {
    (rng, v) = uniform(rng, low: -2.0, high: 2.0, size: 2)
    (x-new, y-new) = (x - v.at(1), y - v.at(0))
    let col = color.mix((blue.transparentize(20%), 1-i/n), (green.transparentize(20%), i/n))
    line(stroke: (paint: col, cap: "round", thickness: 2pt),
      (x, y), (x-new, y-new)
    )
    (x, y) = (x-new, y-new)
  }
})

random-walk

Another example is drawing the the famous Matrix rain effect of falling green characters in a terminal.

#import "@preview/suiji:0.2.0": *
#import "@preview/cetz:0.2.2"

#set page(width: auto, height: auto, margin: 0pt)

#cetz.canvas(length: 1pt, {
  import cetz.draw: *

  let font-size = 10
  let num-col = 80
  let num-row = 32
  let text-len = 16
  let seq = "abcdefghijklmnopqrstuvwxyz!@#$%^&*".split("").slice(1, 35).map(it => raw(it))
  let rng = gen-rng(42)
  let num-cnt = 0
  let val = 0
  let chars = ()

  rect((-10, -10), (font-size * (num-col - 1) * 0.6 + 10, font-size * (num-row - 1) + 10), fill: black)

  for c in range(num-col) {
    (rng, num-cnt) = integers(rng, low: 1, high: 3)
    for cnt in range(num-cnt) {
      (rng, val) = integers(rng, low: -10, high: num-row - 2)
      (rng, chars) = choice(rng, seq, size: text-len)
      for i in range(text-len) {
        let y = i + val
        if y >= 0 and y < num-row {
          let col = green.transparentize((i / text-len) * 100%)
          content(
            (c * font-size * 0.6, y * font-size),
            text(size: font-size * 1pt, fill:col, stroke: (text-len - i) * 0.04pt + col, chars.at(i))
          )
        }
      }
    }
  }
})

matrix-rain

Usage

Import suiji module first before use any random function from it.

#import "@preview/suiji:0.2.0": *

At first, a random number generator object (rng) should be created by function gen-rng, with an integer as the argument of seed.

Other functions have a similar usage style. The object of rng and other parameters are provided as input arguments, and the function return value is an array of two objects, where the first one is an updated object rng and the second one is the desired value. It seems a little inconvenient, as limited by the programming paradigm.

The code below generates several random permutations of 0 to 9. Each time after function shuffle is called, the value of variable rng is updated, so generated permutations are different.

#{
  let rng = gen-rng(42)
  let a = ()
  for i in range(5) {
    (rng, a) = shuffle(rng, range(10))
    [#(a.map(it => str(it)).join("  ")) \ ]
  }
}

random-permutation

For more codes with these functions refer to tests.

Reference

gen-rng

Construct a new random number generator with a seed.

#let gen-rng(seed) = {...}
  • Input Arguments

    • seed : [int] value of seed.
  • Output Arguments

    • rng : [object] generated object of random number generator.

integers

Return random integers from low (inclusive) to high (exclusive).

#let integers(rng, low: 0, high: 100, size: 1, endpoint: false) = {...}
  • Input Arguments

    • rng : [object] object of random number generator.
    • low : [int] lowest (signed) integers to be drawn from the distribution, optional.
    • high : [int] one above the largest (signed) integer to be drawn from the distribution, optional.
    • size : [int] returned array size, must be positive integer, optional.
    • endpoint : [bool] if true, sample from the interval [low, high] instead of the default [low, high), optional.
  • Output Arguments

    • [array] : (rng-out, arr-out)
      • rng-out : [object] updated object of random number generator.
      • arr-out : [int | array of int] array of random numbers.

random

Return random floats in the half-open interval [0.0, 1.0).

#let random(rng, size: 1) = {...}
  • Input Arguments

    • rng : [object] object of random number generator.
    • size : [int] returned array size, must be positive integer, optional.
  • Output Arguments

    • [array] : (rng-out, arr-out)
      • rng-out : [object] updated object of random number generator.
      • arr-out : [float | array of float] array of random numbers.

uniform

Draw samples from a uniform distribution. Samples are uniformly distributed over the half-open interval [low, high) (includes low, but excludes high).

#let uniform(rng, low: 0.0, high: 1.0, size: 1) = {...}
  • Input Arguments

    • rng : [object] object of random number generator.
    • low : [float] lower boundary of the output interval, optional.
    • high : [float] upper boundary of the output interval, optional.
    • size : [int] returned array size, must be positive integer, optional.
  • Output Arguments

    • [array] : (rng-out, arr-out)
      • rng-out : [object] updated object of random number generator.
      • arr-out : [float | array of float] array of random numbers.

normal

Draw random samples from a normal (Gaussian) distribution.

#let normal(rng, loc: 0.0, scale: 1.0, size: 1) = {...}
  • Input Arguments

    • rng : [object] object of random number generator.
    • loc : [float] mean (centre) of the distribution, optional.
    • scale : [float] standard deviation (spread or width) of the distribution, must be non-negative, optional.
    • size : [int] returned array size, must be positive integer, optional.
  • Output Arguments

    • [array] : (rng-out, arr-out)
      • rng-out : [object] updated object of random number generator.
      • arr-out : [float | array of float] array of random numbers.

shuffle

Randomly shuffle a given array.

#let shuffle(rng, arr) = {...}
  • Input Arguments

    • rng : [object] object of random number generator.
    • arr : [array] the array to be shuffled.
  • Output Arguments

    • [array] : (rng-out, arr-out)
      • rng-out : [object] updated object of random number generator.
      • arr-out : [array] shuffled array.

choice

Generates random samples from a given array.

#let choice(rng, arr, size: 1, replacement: true, permutation: true) = {...}
  • Input Arguments

    • rng : [object] object of random number generator.
    • arr : [array] the array to be sampled.
    • size : [int] returned array size, must be positive integer, optional.
    • replacement: [bool] whether the sample is with or without replacement, optional; default is true, meaning that a value of arr can be selected multiple times.
    • permutation: [bool] whether the sample is permuted when sampling without replacement, optional; default is true, false provides a speedup.
  • Output Arguments

    • [array] : (rng-out, arr-out)
      • rng-out : [object] updated object of random number generator.
      • arr-out : [array] generated random samples.