Skip to content

Commit d06b66d

Browse files
committed
Merge pull request #34 from hdgarrood/adjust-lcg
Adjust LCG parameters
2 parents b15b7c0 + 89a767e commit d06b66d

6 files changed

Lines changed: 115 additions & 42 deletions

File tree

docs/Test/QuickCheck.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ representing the number of tests which should be run.
4949
#### `quickCheckPure`
5050

5151
``` purescript
52-
quickCheckPure :: forall prop. (Testable prop) => Int -> Int -> prop -> List Result
52+
quickCheckPure :: forall prop. (Testable prop) => Seed -> Int -> prop -> List Result
5353
```
5454

5555
Test a property, returning all test results as an array.

docs/Test/QuickCheck/Gen.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,21 +172,29 @@ evalGen :: forall a. Gen a -> GenState -> a
172172

173173
Run a random generator, keeping only the randomly-generated result
174174

175-
#### `showSample'`
175+
#### `sample`
176176

177177
``` purescript
178-
showSample' :: forall r a. (Show a) => Size -> Gen a -> Eff (console :: CONSOLE | r) Unit
178+
sample :: forall r a. Seed -> Size -> Gen a -> Array a
179179
```
180180

181-
Print a random sample to the console
181+
Sample a random generator
182182

183-
#### `showSample`
183+
#### `randomSample'`
184184

185185
``` purescript
186-
showSample :: forall r a. (Show a) => Gen a -> Eff (console :: CONSOLE | r) Unit
186+
randomSample' :: forall r a. Size -> Gen a -> Eff (random :: RANDOM | r) (Array a)
187187
```
188188

189-
Print a random sample of 10 values to the console
189+
Sample a random generator, using a randomly generated seed
190+
191+
#### `randomSample`
192+
193+
``` purescript
194+
randomSample :: forall r a. Gen a -> Eff (random :: RANDOM | r) (Array a)
195+
```
196+
197+
Get a random sample of 10 values
190198

191199
#### `uniform`
192200

docs/Test/QuickCheck/LCG.md

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,36 @@
11
## Module Test.QuickCheck.LCG
22

3-
#### `Seed`
4-
5-
``` purescript
6-
type Seed = Int
7-
```
8-
93
#### `lcgM`
104

115
``` purescript
126
lcgM :: Int
137
```
148

15-
A magic constant for the linear congruential generator
9+
The *multiplier*: a magic constant for the linear congruential generator
1610

1711
#### `lcgC`
1812

1913
``` purescript
2014
lcgC :: Int
2115
```
2216

23-
A magic constant for the linear congruential generator
17+
The *increment*: a magic constant for the linear congruential generator
2418

2519
#### `lcgN`
2620

2721
``` purescript
2822
lcgN :: Int
2923
```
3024

31-
A magic constant for the linear congruential generator
25+
The *modulus*: a magic constant for the linear congruential generator.
26+
It is equal to 2^31 - 1, a Mersenne prime. It is useful for this value to
27+
be prime, because then the requirement of the initial seed being coprime
28+
to the modulus is satisfied when the seed is between 1 and lcgN - 1.
3229

3330
#### `lcgNext`
3431

3532
``` purescript
36-
lcgNext :: Int -> Int
33+
lcgNext :: Seed -> Seed
3734
```
3835

3936
Step the linear congruential generator
@@ -46,4 +43,32 @@ randomSeed :: forall e. Eff (random :: RANDOM | e) Seed
4643

4744
Create a random seed
4845

46+
#### `Seed`
47+
48+
``` purescript
49+
newtype Seed
50+
```
51+
52+
A seed for the linear congruential generator. We omit a `Semiring`
53+
instance because there is no `zero` value, as 0 is not an acceptable
54+
seed for the generator.
55+
56+
##### Instances
57+
``` purescript
58+
instance showSeed :: Show Seed
59+
instance eqSeed :: Eq Seed
60+
```
61+
62+
#### `mkSeed`
63+
64+
``` purescript
65+
mkSeed :: Int -> Seed
66+
```
67+
68+
#### `runSeed`
69+
70+
``` purescript
71+
runSeed :: Seed -> Int
72+
```
73+
4974

src/Test/QuickCheck.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ quickCheck' n prop = do
6565
-- |
6666
-- | The first argument is the _random seed_ to be passed to the random generator.
6767
-- | The second argument is the number of tests to run.
68-
quickCheckPure :: forall prop. (Testable prop) => Int -> Int -> prop -> List Result
68+
quickCheckPure :: forall prop. (Testable prop) => Seed -> Int -> prop -> List Result
6969
quickCheckPure s n prop = evalGen (replicateM n (test prop)) { newSeed: s, size: 10 }
7070

7171
-- | The `Testable` class represents _testable properties_.

src/Test/QuickCheck/Gen.purs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ module Test.QuickCheck.Gen
2222
, evalGen
2323
, perturbGen
2424
, uniform
25-
, showSample
26-
, showSample'
25+
, sample
26+
, randomSample
27+
, randomSample'
2728
) where
2829

2930
import Prelude
3031

3132
import Control.Monad.Eff (Eff())
32-
import Control.Monad.Eff.Console (CONSOLE(), print)
33+
import Control.Monad.Eff.Random (RANDOM())
3334
import Data.Array ((!!), length, range)
3435
import Data.Foldable (fold)
3536
import Data.Int (fromNumber, toNumber)
@@ -148,21 +149,23 @@ evalGen :: forall a. Gen a -> GenState -> a
148149
evalGen gen st = (runGen gen st).value
149150

150151
-- | Sample a random generator
151-
sample :: forall r a. Size -> Gen a -> Array a
152-
sample sz g = evalGen (vectorOf sz g) { newSeed: zero, size: sz }
152+
sample :: forall r a. Seed -> Size -> Gen a -> Array a
153+
sample seed sz g = evalGen (vectorOf sz g) { newSeed: seed, size: sz }
153154

154-
-- | Print a random sample to the console
155-
showSample' :: forall r a. (Show a) => Size -> Gen a -> Eff (console :: CONSOLE | r) Unit
156-
showSample' n g = print $ sample n g
155+
-- | Sample a random generator, using a randomly generated seed
156+
randomSample' :: forall r a. Size -> Gen a -> Eff (random :: RANDOM | r) (Array a)
157+
randomSample' n g = do
158+
seed <- randomSeed
159+
return $ sample seed n g
157160

158-
-- | Print a random sample of 10 values to the console
159-
showSample :: forall r a. (Show a) => Gen a -> Eff (console :: CONSOLE | r) Unit
160-
showSample = showSample' 10
161+
-- | Get a random sample of 10 values
162+
randomSample :: forall r a. Gen a -> Eff (random :: RANDOM | r) (Array a)
163+
randomSample = randomSample' 10
161164

162165
-- | A random generator which simply outputs the current seed
163166
lcgStep :: Gen Int
164167
lcgStep = Gen f where
165-
f s = { value: s.newSeed, state: s { newSeed = lcgNext s.newSeed } }
168+
f s = { value: runSeed s.newSeed, state: s { newSeed = lcgNext s.newSeed } }
166169

167170
-- | A random generator which approximates a uniform random variable on `[0, 1]`
168171
uniform :: Gen Number
@@ -172,7 +175,9 @@ foreign import float32ToInt32 :: Number -> Int
172175

173176
-- | Perturb a random generator by modifying the current seed
174177
perturbGen :: forall a. Number -> Gen a -> Gen a
175-
perturbGen n (Gen f) = Gen $ \s -> f (s { newSeed = lcgNext (float32ToInt32 n) + s.newSeed })
178+
perturbGen n (Gen f) = Gen $ \s -> f (s { newSeed = perturb s.newSeed })
179+
where
180+
perturb oldSeed = mkSeed (runSeed (lcgNext (mkSeed (float32ToInt32 n))) + runSeed oldSeed)
176181

177182
instance functorGen :: Functor Gen where
178183
map f (Gen g) = Gen $ \s -> case g s of

src/Test/QuickCheck/LCG.purs

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module Test.QuickCheck.LCG
22
( Seed()
3+
, mkSeed
4+
, runSeed
35
, lcgM
46
, lcgC
57
, lcgN
@@ -16,24 +18,57 @@ import Data.Int (fromNumber, toNumber)
1618
import Data.Int.Bits (shl)
1719
import qualified Data.Maybe.Unsafe as U
1820

19-
type Seed = Int
20-
21-
-- | A magic constant for the linear congruential generator
21+
-- | The *multiplier*: a magic constant for the linear congruential generator
2222
lcgM :: Int
23-
lcgM = 1103515245
23+
lcgM = 48271
2424

25-
-- | A magic constant for the linear congruential generator
25+
-- | The *increment*: a magic constant for the linear congruential generator
2626
lcgC :: Int
27-
lcgC = 12345
27+
lcgC = 0
2828

29-
-- | A magic constant for the linear congruential generator
29+
-- | The *modulus*: a magic constant for the linear congruential generator.
30+
-- | It is equal to 2^31 - 1, a Mersenne prime. It is useful for this value to
31+
-- | be prime, because then the requirement of the initial seed being coprime
32+
-- | to the modulus is satisfied when the seed is between 1 and lcgN - 1.
3033
lcgN :: Int
31-
lcgN = one `shl` 30
34+
lcgN = 2147483647
3235

3336
-- | Step the linear congruential generator
34-
lcgNext :: Int -> Int
35-
lcgNext n = U.fromJust $ fromNumber $ (toNumber lcgM * toNumber n + toNumber lcgC) % toNumber lcgN
37+
lcgNext :: Seed -> Seed
38+
lcgNext = Seed <<< go <<< runSeed
39+
where
40+
go n = U.fromJust $ fromNumber $ (toNumber lcgM * toNumber n + toNumber lcgC) % toNumber lcgN
3641

3742
-- | Create a random seed
3843
randomSeed :: forall e. Eff (random :: RANDOM | e) Seed
39-
randomSeed = randomInt 0 lcgM
44+
randomSeed = mkSeed <$> randomInt seedMin seedMax
45+
46+
-- | The minimum permissible Seed value.
47+
seedMin :: Int
48+
seedMin = 1
49+
50+
-- | The maximum permissible Seed value.
51+
seedMax :: Int
52+
seedMax = lcgM - 1
53+
54+
-- | A seed for the linear congruential generator. We omit a `Semiring`
55+
-- | instance because there is no `zero` value, as 0 is not an acceptable
56+
-- | seed for the generator.
57+
newtype Seed = Seed Int
58+
59+
mkSeed :: Int -> Seed
60+
mkSeed x = Seed (ensureBetween seedMin seedMax x)
61+
62+
runSeed :: Seed -> Int
63+
runSeed (Seed x) = x
64+
65+
ensureBetween :: Int -> Int -> Int -> Int
66+
ensureBetween min max n =
67+
let rangeSize = max - min
68+
in (((n `mod` rangeSize) + rangeSize) `mod` rangeSize) + min
69+
70+
instance showSeed :: Show Seed where
71+
show (Seed x) = "Seed " <> show x
72+
73+
instance eqSeed :: Eq Seed where
74+
eq (Seed x) (Seed y) = eq x y

0 commit comments

Comments
 (0)