Repository navigation
Expand file tree
/
Copy pathRandom.hs
More file actions
145 lines (120 loc) · 5.34 KB
/
Copy pathRandom.hs
File metadata and controls
145 lines (120 loc) · 5.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
-- |
-- Basic pseudo-random number generation.
--
-- Information on performance of a selection of pseudo-random number generation
-- algorithms, including those implemented below, can be found at:
-- https://prng.di.unimi.it/
--
-- Important: these pseudo-random number generators are NOT cryptographically
-- secure generators.
module Copilot.Library.Random
( randomW32
, randomW64
, boundedRandomIntegral
, proportionalRandomBool
, weightedRandomSelection
)
where
-- External imports
import Copilot.Language (Array, Stream, Typed, Word32, Word64, arrayElems,
constant, mod, mux, (!), (++), (.<<.), (.>>.), (.^.),
(<), (<=))
import GHC.TypeLits (KnownNat, type (<=))
import Prelude hiding (mod, (++), (<), (<=))
-- | Given two Word32 seeds, produce a pseudo-random stream of Word32 numbers,
-- which has a period of 2^32 - 1. Neither of the seeds should be zero, since
-- the underlying randomization algorithm maps zero to itself.
--
-- This function uses an Xorshift+ algorithm.
randomW32 :: Word32 -> Word32 -> Stream Word32
randomW32 0 _ = error "randomW32: seed cannot be zero."
randomW32 _ 0 = error "randomW32: seed cannot be zero."
randomW32 seed1 seed2 = s1 + s2
where
s1 :: Stream Word32
s1 = [seed1] ++ (xorshift32 s1)
s2 :: Stream Word32
s2 = [seed2] ++ (xorshift32 s2)
-- This is a simple implementation of an Xorshift algorithm
xorshift32 :: Stream Word32 -> Stream Word32
xorshift32 x1 = x4 -- Note: 0 maps to itself with this
where
x4 = x3 .^. (x3 .<<. (5 :: Stream Word32))
x3 = x2 .^. (x2 .>>. (17 :: Stream Word32))
x2 = x1 .^. (x1 .<<. (13 :: Stream Word32))
-- | Given two Word64 seeds, produce a pseudo-random stream of Word64 numbers,
-- which has a period of 2^128 - 1. Neither of the seeds should be zero, as
-- doing so will negatively impact the quality of the randomness.
--
-- This function uses an Xorshiftrplus algorithm.
randomW64 :: Word64 -> Word64 -> Stream Word64
randomW64 0 _ = error "randomW64: seed cannot be zero."
randomW64 _ 0 = error "randomW64: seed cannot be zero."
randomW64 seed1 seed2 = x
where
s0 :: Stream Word64
s0 = [seed1] ++ s1
s1 :: Stream Word64
s1 = [seed2] ++ (x + s1)
x :: Stream Word64
x = xorshiftr128plus s0 s1
xorshiftr128plus :: Stream Word64 -> Stream Word64 -> Stream Word64
xorshiftr128plus x1 y = x4
where
x4 = x3 .^. y
x3 = x2 .^. (x2 .>>. (17 :: Stream Word64))
x2 = x1 .^. (x1 .<<. (23 :: Stream Word64))
-- | Given a stream of random integral numbers, a minimum value, and a maximum
-- value, produce a stream of random values greater than or equal to the
-- minimum and less than the maximum value, i.e., [@mN@, @mX@).
boundedRandomIntegral :: (Typed a, Integral a)
=> Stream a -> a -> a -> Stream a
boundedRandomIntegral gen mN mX = (gen `mod` constant (mX - mN)) + constant mN
-- | Given a stream of random integral numbers and two integers defining the
-- numerator and denominator of a rational probability, produce a boolean
-- stream that is randomly @True@ with the given probability. That is to say,
-- given a numerator @n@ and denominator @d@, then, over a sufficiently large
-- sample size, the resulting stream @s@ is @True@ @n@ out of @d@ times.
proportionalRandomBool :: (Typed a, Integral a)
=> Stream a -> a -> a -> Stream Bool
proportionalRandomBool gen n d = (gen `mod` constant d) < constant n
-- | Given a stream of random integral numbers, an array of values, and an
-- equal length array of weights, produce a stream of random values from the
-- given array with probabilities proportional to the given weights.
weightedRandomSelection :: forall a b s
. (KnownNat s, s <= MaxWord32, Typed a, Typed b,
Integral a, Bounded a)
=> Stream a -> Array s b -> Array s a -> Stream b
weightedRandomSelection gen values weights
| overflow
= error "weightedRandomSelection: sum of weights overflows type"
| signedType
= error "weightedRandomSelection: type of weights array must be unsigned"
| otherwise
= constant values ! idx
where
-- Check if cumulativeSum decreases at any point. For unsigned integral
-- types, a decreasing element in this cumulative sequence corresponds to
-- an overflow.
overflow = or (zipWith (>) cumulativeSum (drop 1 cumulativeSum))
signedType = 0 > (minBound :: a)
idx :: Stream Word32
idx = countLeq cumulativeSum (gen `mod` constant totalSum)
countLeq :: [a] -> Stream a -> Stream Word32
countLeq xs v = sum [ mux (constant x <= v) 1 0 | x <- xs ]
-- ^ get number of elements in @xs@ less than or equal to @v@.
-- For non-decreasing @xs@, this is equal to the index of the first
-- element greater than @v@.
cumulativeSum :: [a]
cumulativeSum = scanl1 (+) (arrayElems weights)
totalSum :: a
totalSum = last cumulativeSum
-- | Type-level representation of the upper bound on an unsigned 32-bit
-- integer.
type MaxWord32 = 4294967295 -- 2^32 - 1