GaussianSampler   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 92
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 2
Bugs 1 Features 1
Metric Value
wmc 14
c 2
b 1
f 1
lcom 1
cbo 1
dl 0
loc 92
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 25 3
C nextSample() 0 23 7
A sampleTail() 0 16 4
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