1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Savvot\Random; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Gaussian sampler for generating normally distributed random numbers. |
7
|
|
|
* Based on Ziggurat algorithm: https://en.wikipedia.org/wiki/Ziggurat_algorithm |
8
|
|
|
* PHP implementation based on: http://svn.code.sf.net/p/sharpneat/code/branches/V2/src/SharpNeatLib/Utility/ZigguratGaussianSampler.cs |
9
|
|
|
* |
10
|
|
|
* @package Savvot\Random |
11
|
|
|
* @author SavvoT <[email protected]> |
12
|
|
|
*/ |
13
|
|
|
class GaussianSampler |
14
|
|
|
{ |
15
|
|
|
const BLOCK_COUNT = 128; |
16
|
|
|
const R = 3.442619855899; |
17
|
|
|
const A = 9.91256303526217e-3; |
18
|
|
|
|
19
|
|
|
/** @var AbstractRand RNG source */ |
20
|
|
|
private $rng; |
21
|
|
|
|
22
|
|
|
private $intMaxDiv; |
23
|
|
|
private $x = []; |
24
|
|
|
private $y = []; |
25
|
|
|
private $xComp = []; |
26
|
|
|
private $ay0; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Creates sampler with given uniform PRNG source |
30
|
|
|
* |
31
|
|
|
* @param AbstractRand $rng Uniform PRNG |
32
|
|
|
*/ |
33
|
|
|
public function __construct(AbstractRand $rng) |
34
|
|
|
{ |
35
|
|
|
$this->rng = $rng; |
36
|
|
|
$this->intMaxDiv = 1.0 / $rng::INT_MAX; |
37
|
|
|
|
38
|
|
|
$this->x[0] = self::R; |
39
|
|
|
$this->y[0] = exp(-(self::R * self::R / 2.0)); |
40
|
|
|
|
41
|
|
|
$this->x[1] = self::R; |
42
|
|
|
$this->y[1] = $this->y[0] + (self::A / $this->x[1]); |
43
|
|
|
|
44
|
|
|
for ($i = 2; $i < self::BLOCK_COUNT; $i++) { |
45
|
|
|
$this->x[$i] = sqrt(-2.0 * log($this->y[$i - 1])); |
46
|
|
|
$this->y[$i] = $this->y[$i - 1] + (self::A / $this->x[$i]); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
$this->x[self::BLOCK_COUNT] = 0.0; |
50
|
|
|
$this->ay0 = self::A / $this->y[0]; |
51
|
|
|
|
52
|
|
|
$this->xComp[0] = (int)(((self::R * $this->y[0]) / self::A) * $rng::INT_MAX); |
53
|
|
|
for ($i = 1; $i < self::BLOCK_COUNT - 1; $i++) { |
54
|
|
|
$this->xComp[$i] = (int)(($this->x[$i + 1] / $this->x[$i]) * $rng::INT_MAX); |
55
|
|
|
} |
56
|
|
|
$this->xComp[self::BLOCK_COUNT - 1] = 0; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Generates standard normally distributed random number (mean = 0.0, deviation = 1.0) |
61
|
|
|
* |
62
|
|
|
* @return float Random number |
63
|
|
|
*/ |
64
|
|
|
public function nextSample() |
65
|
|
|
{ |
66
|
|
|
while (true) { |
67
|
|
|
$u = $this->rng->random(0, 255); |
68
|
|
|
$i = $u & 0x7f; |
69
|
|
|
$sign = (($u & 0x80) === 0) ? -1 : 1; |
70
|
|
|
$u2 = $this->rng->randomInt(); |
71
|
|
|
|
72
|
|
|
if ($i === 0) { |
73
|
|
|
if ($u2 < $this->xComp[0]) { |
74
|
|
|
return $u2 * $this->intMaxDiv * $this->ay0 * $sign; |
75
|
|
|
} |
76
|
|
|
return $this->sampleTail() * $sign; |
77
|
|
|
} |
78
|
|
|
if ($u2 < $this->xComp[$i]) { |
79
|
|
|
return $u2 * $this->intMaxDiv * $this->x[$i] * $sign; |
80
|
|
|
} |
81
|
|
|
$x = $u2 * $this->intMaxDiv * $this->x[$i]; |
82
|
|
|
if ($this->y[$i - 1] + (($this->y[$i] - $this->y[$i - 1]) * $this->rng->randomFloat()) < exp(-($x * $x / 2.0))) { |
83
|
|
|
return $x * $sign; |
84
|
|
|
} |
85
|
|
|
} |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
private function sampleTail() |
89
|
|
|
{ |
90
|
|
|
do { |
91
|
|
|
do { |
92
|
|
|
$f = $this->rng->randomFloat(); |
93
|
|
|
} while ($f == 0); |
94
|
|
|
$x = -log($f) / self::R; |
95
|
|
|
|
96
|
|
|
do { |
97
|
|
|
$f = $this->rng->randomFloat(); |
98
|
|
|
} while ($f == 0); |
99
|
|
|
$y = -log($f); |
100
|
|
|
} while (($y + $y) < ($x * $x)); |
101
|
|
|
|
102
|
|
|
return self::R + $x; |
103
|
|
|
} |
104
|
|
|
} |
105
|
|
|
|