Skip to content

Commit

Permalink
Implement PCG (#1)
Browse files Browse the repository at this point in the history
* Update Roc

* Implement PCG-RXS-M-XS

See section 6.3.4 (page 45) of https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf
and https://github.com/imneme/pcg-c/blob/83252d9c23df9c82ecb42210afed61a7b42402d7/include/pcg_variants.h#L188

* Fix typo

* Improve comment formatting for docsgen

* Update Roc

* Un-privatize Seed tags

* Ignore experimental docs website (generated by `roc docs`)

* Fix type alias typo

* Update Roc

* Disable broken code with TODOs for blockers

* Fix order-of-operations mistake in `between` algorithm

* Refactor & wrap PCG algorithms

* Add necessary type annotations

* Add clarifying type annotations

* Add another example point to show `.next` determinacy

* Improve example ranges

* Improve TODO messages

* Update Roc

* Use the new min/max definitions

* Add `.u8` & `.u16`

* Expose types

* Update example to use `.u8` & `.u16`

* Add example to README
  • Loading branch information
JanCVanB authored Jan 18, 2022
1 parent f33d59f commit afd30b2
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
example
example.ll
generated-docs
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ This is a work in progress.

Contributions & feedback are very welcome! :)

## Example

See [this example module](https://github.com/JanCVanB/roc-random/blob/main/example.roc)
for some examples of how to use this library.

## Goals

* An external API that is similar to that of
Expand Down
322 changes: 306 additions & 16 deletions Random.roc
Original file line number Diff line number Diff line change
@@ -1,33 +1,323 @@
interface Random
exposes [
Generation,
Generator,
Seed,
Generation,
Seed8,
Seed16,
Seed32,
int,
next,
seed8,
seed16,
seed32,
step,
u8,
u16,
u32,
]
imports []


Seed : U64
Generator a : Seed -> Generation a
# TODO: Can we simplify this type? `x.value` is awkward.
Generation a : { value : a, seed : Seed }
## ## Types

Generator seed value : seed -> Generation seed value
Generation seed value : { seed, value }

Seed8 : [ Seed8 U8 ]*
Seed16 : [ Seed16 U16 ]*
Seed32 : [ Seed32 U32 ]*

next : Generation *, Generator a -> Generation a

## ## Helpers for all generators

next : Generation seed *, Generator seed value -> Generation seed value
next = \x, g -> g x.seed

step : Seed, Generator a -> Generation a
step = \seed, g -> g seed
seed8 : U8 -> Seed8
seed8 = \state -> Seed8 state

seed16 : U16 -> Seed16
seed16 = \state -> Seed16 state

seed32 : U32 -> Seed32
seed32 = \state -> Seed32 state

step : seed, Generator seed value -> Generation seed value
step = \s, g -> g s


## ## Constructors for primitive generators

int = u32

# TODO: This is waiting on `convertU8ToI8`.
# i8 : I8, I8 -> Generator Seed8 I8
# i8 = \x, y ->
# between x y (\seed -> convertU8ToI8 (growSeed8 seed)) (\seed -> updateSeed8 seed)

# TODO: This is waiting on `convertU16ToI16`.
# i16 : I16, I16 -> Generator Seed16 I16
# i16 = \x, y ->
# between x y (\seed -> convertU16ToI16 (growSeed16 seed)) (\seed -> updateSeed16 seed)

# TODO: This is waiting on `convertU32ToI32`.
# i32 : I32, I32 -> Generator Seed32 I32
# i32 = \x, y ->
# between x y (\seed -> convertU32ToI32 (growSeed32 seed)) (\seed -> updateSeed32 seed)

# TODO: This is waiting on `convertU64ToI64` & `growseed64`.
# i64 : I64, I64 -> Generator Seed64 I64
# i64 = \x, y ->
# between x y (\seed -> convertU64ToI64 (growSeed64 seed)) (\seed -> updateSeed64 seed)

# TODO: This is waiting on `convertU128ToI128` & `growseed128`.
# i128 : I128, I128 -> Generator Seed128 I128
# i128 = \x, y ->
# between x y (\seed -> convertU128ToI128 (growSeed128 seed)) (\seed -> updateSeed128 seed)

u8 : U8, U8 -> Generator Seed8 U8
u8 = \x, y ->
between x y (\seed -> growSeed8 seed) (\seed -> updateSeed8 seed)

u16 : U16, U16 -> Generator Seed16 U16
u16 = \x, y ->
between x y (\seed -> growSeed16 seed) (\seed -> updateSeed16 seed)

u32 : U32, U32 -> Generator Seed32 U32
u32 = \x, y ->
between x y (\seed -> growSeed32 seed) (\seed -> updateSeed32 seed)

# TODO: This is waiting on `growSeed64`.
# u64 : U64, U64 -> Generator Seed64 U64
# u64 = \x, y ->
# between x y (\seed -> growSeed64 seed) (\seed -> updateSeed64 seed)

# TODO: This is waiting on `growSeed64`.
# u128 : U128, U128 -> Generator Seed128 U128
# u128 = \x, y ->
# between x y (\seed -> growSeed128 seed) (\seed -> updateSeed128 seed)


#### Generator helpers

int : Int a, Int a -> Generator (Int a)
int = \a, b ->
between = \x, y, growSeed, updateSeed ->
Pair minimum maximum = sort x y
range = maximum - minimum + 1
\seed ->
# TODO: Replace this placeholder implementation with PCG.
if (seed % 2 |> Result.withDefault 0) == 0 then
{ value: a, seed: seed + 1 }
else
{ value: b, seed: seed + 1 }
value = minimum + modWithNonzero (growSeed seed) range
{ value, seed: updateSeed seed }

# TODO: This is waiting on `Num.toI8` (https://github.com/rtfeldman/roc/issues/664).
# convertU8ToI8 : U8 -> I8
# convertU8ToI8 = \x ->
# minimum : I8
# minimum = Num.minI8
# maximum : I8
# maximum = Num.maxI8
# if x <= maximum then
# Num.toI8 x + minimum
# else
# Num.toI8 (x + minimum)

# TODO: This is waiting on `Num.toI16` (https://github.com/rtfeldman/roc/issues/664).
# convertU16ToI16 : U16 -> I16
# convertU16ToI16 = \x ->
# minimum : I16
# minimum = Num.minI16
# maximum : I16
# maximum = Num.maxI16
# if x <= maximum then
# Num.toI16 x + minimum
# else
# Num.toI16 (x + minimum)

# TODO: This is waiting on `Num.toI32` (https://github.com/rtfeldman/roc/issues/664).
# convertU32ToI32 : U32 -> I32
# convertU32ToI32 = \x ->
# minimum : I32
# minimum = Num.minI32
# maximum : I32
# maximum = Num.maxI32
# if x <= maximum then
# Num.toI32 x + minimum
# else
# Num.toI32 (x + minimum)

# TODO: This is waiting on `Num.toI64` (https://github.com/rtfeldman/roc/issues/664).
# convertU64ToI64 : U64 -> I64
# convertU64ToI64 = \x ->
# minimum : I64
# minimum = Num.minI64
# maximum : I64
# maximum = Num.maxI64
# if x <= maximum then
# Num.toI64 x + minimum
# else
# Num.toI64 (x + minimum)

# TODO: This is waiting on `Num.toI128` (https://github.com/rtfeldman/roc/issues/664).
# convertU128ToI128 : U128 -> I128
# convertU128ToI128 = \x ->
# minimum : I128
# minimum = Num.minI128
# maximum : I128
# maximum = Num.maxI128
# if x <= maximum then
# Num.toI128 x + minimum
# else
# Num.toI128 (x + minimum)

# Warning: y must never equal 0. The `123` fallback is nonsense for typechecking only.
modWithNonzero = \x, y -> x % y |> Result.withDefault 123

sort = \x, y ->
if x < y then
Pair x y
else
Pair y x


#### PCG algorithms & wrappers
#
# Based on this paper: https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf
# Based on this C++ header: https://github.com/imneme/pcg-c/blob/master/include/pcg_variants.h
# Abbreviations:
# M = Multiplication (see section 6.3.4 on page 45 in the paper)
# PCG = Permuted Congruential Generator
# RXS = Random XorShift (see section 5.5.1 on page 36 in the paper)
# XS = XorShift (see section 5.5 on page 34 in the paper)

# See `pcg_output_rxs_m_xs_8_8` (on line 170?) in the C++ header.
growSeed8 : Seed8 -> U8
growSeed8 = \Seed8 state ->
rxs : U8
rxs = 6
rxsi : U8
rxsi = 2
m : U8
m = 217
xs : U8
xs = 6
pcgRxsMXs state rxs rxsi m xs

# See `pcg_output_rxs_m_xs_16_16` (on line 182?) in the C++ header.
growSeed16 : Seed16 -> U16
growSeed16 = \Seed16 state ->
rxs : U16
rxs = 13
rxsi : U16
rxsi = 3
m : U16
m = 62169
xs : U16
xs = 11
pcgRxsMXs state rxs rxsi m xs

# See `pcg_output_rxs_m_xs_32_32` (on line 176?) in the C++ header.
growSeed32 : Seed32 -> U32
growSeed32 = \Seed32 state ->
rxs : U32
rxs = 28
rxsi : U32
rxsi = 4
m : U32
m = 277_803_737
xs : U32
xs = 22
pcgRxsMXs state rxs rxsi m xs

# TODO: This is waiting on literals > maxI64 (https://github.com/rtfeldman/roc/issues/2332).
# # See `pcg_output_rxs_m_xs_64_64` (on line 188?) in the C++ header.
# growSeed64 = Seed64 -> U64
# growSeed64 = \Seed64 state ->
# rxs : U64
# rxs = 59
# rxsi : U64
# rxsi = 5
# m : U64
# m = 12_605_985_483_714_917_081
# xs : U64
# xs = 43
# pcgRxsMXs state rxs rxsi m xs

# TODO: This is waiting on literals > maxI64 (https://github.com/rtfeldman/roc/issues/2332).
# # See `pcg_output_rxs_m_xs_128_128` (on line 196?) in the C++ header.
# growSeed128 = Seed128 -> U128
# growSeed128 = \Seed128 state ->
# rxs : U128
# rxs = 122
# rxsi : U128
# rxsi = 6
# m : U128
# m = (Num.shiftLeftBy 64 17_766_728_186_571_221_404) + 12_605_985_483_714_917_081
# xs : U128
# xs = 86
# pcgRxsMXs state rxs rxsi m xs

# See section 6.3.4 on page 45 in the paper.
pcgRxsMXs : Int a, Int a, Int a, Int a, Int a -> Int a
pcgRxsMXs = \state, randomBitshift, randomBitshiftIncrement, multiplier, bitshift ->
partial = Num.mulWrap multiplier (
Num.bitwiseXor state (
Num.shiftRightZfBy (
Num.addWrap (Num.shiftRightZfBy randomBitshift state) randomBitshiftIncrement
) state
))
Num.bitwiseXor partial (Num.shiftRightZfBy bitshift partial)

# See section 4.1 on page 20 in the paper.
pcgUpdateState : Int a, Int a, Int a -> Int a
pcgUpdateState = \state, multiplier, increment ->
Num.addWrap (Num.mulWrap state multiplier) increment

# See `pcg_oneseq_8_step_r` (line 409?) in the above C++ header
updateSeed8 : Seed8 -> Seed8
updateSeed8 = \Seed8 state ->
multiplier : U8
multiplier = 141
# TODO: Replace this with user-supplied?
increment : U8
increment = 77
Seed8 (pcgUpdateState state multiplier increment)

# See `pcg_oneseq_16_step_r` (line 456?) in the above C++ header
updateSeed16 : Seed16 -> Seed16
updateSeed16 = \Seed16 state ->
multiplier : U16
multiplier = 12829
# TODO: Replace this with user-supplied?
increment : U16
increment = 47989
Seed16 (pcgUpdateState state multiplier increment)

# See `pcg_oneseq_32_step_r` (line 504?) in the above C++ header
updateSeed32 : Seed32 -> Seed32
updateSeed32 = \Seed32 state ->
multiplier : U32
multiplier = 747_796_405
# TODO: Replace this with user-supplied?
increment : U32
increment = 2_891_336_453
Seed32 (pcgUpdateState state multiplier increment)

# TODO: This is waiting on 64-bit generators.
# # See `pcg_oneseq_64_step_r` (line 552?) in the above C++ header.
# updateSeed64 : Seed64 -> Seed64
# updateSeed64 = \Seed64 state ->
# multiplier : U64
# multiplier = 6_364_136_223_846_793_005
# # TODO: Replace this with user-supplied?
# increment : U64
# increment = 1_442_695_040_888_963_407
# Seed64 (pcgUpdateState state multiplier increment)

# TODO: This is waiting on 128-bit generators.
# # See `pcg_oneseq_128_step_r` (line 601?) in the above C++ header.
# updateSeed128 : Seed128 -> Seed128
# updateSeed128 = \Seed128 state ->
# multiplier : U128
# multiplier = (Num.shiftLeftBy 64 2_549_297_995_355_413_924) + 4_865_540_595_714_422_341
# # TODO: Replace this with user-supplied?
# increment : U128
# increment = (Num.shiftLeftBy 64 6_364_136_223_846_793_005) + 1_442_695_040_888_963_407
# Seed128 (pcgUpdateState state multiplier increment)
Loading

0 comments on commit afd30b2

Please sign in to comment.