|
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
|
|
|
|