Exponential::random()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
dl 0
loc 16
ccs 0
cts 0
cp 0
crap 2
rs 9.9666
c 1
b 0
f 0
eloc 9
nc 1
nop 0
1
<?php
2
3
namespace Samsara\Fermat\Stats\Values\Distribution;
4
5
use Samsara\Exceptions\UsageError\IntegrityConstraint;
6
use Samsara\Exceptions\UsageError\OptionalExit;
7
use Samsara\Fermat\Core\Numbers;
8
use Samsara\Fermat\Stats\Types\Distribution;
9
use Samsara\Fermat\Core\Provider\RandomProvider;
10
use Samsara\Fermat\Core\Types\Base\Interfaces\Numbers\DecimalInterface;
11
use Samsara\Fermat\Core\Values\ImmutableDecimal;
12
13
class Exponential extends Distribution
14
{
15
16
    /**
17
     * @var ImmutableDecimal
18
     */
19
    private $lambda;
20
21
    /**
22
     * Exponential constructor.
23
     *
24
     * @param int|float|DecimalInterface $lambda This is the *rate parameter* not the *scale parameter*
25
     *
26
     * @throws IntegrityConstraint
27
     */
28 2
    public function __construct($lambda)
29
    {
30 2
        $lambda = Numbers::makeOrDont(Numbers::IMMUTABLE, $lambda);
31
32 2
        if (!$lambda->isPositive()) {
33
            throw new IntegrityConstraint(
34
                'Lambda must be positive',
35
                'Provide a positive lambda',
36
                'Exponential distributions work on time to occurrence; the mean time to occurrence (lambda) must be positive'
37
            );
38
        }
39
40 2
        $this->lambda = $lambda;
41
    }
42
43
    /**
44
     * @param int|float|DecimalInterface $x
45
     *
46
     * @return ImmutableDecimal
47
     * @throws IntegrityConstraint
48
     */
49
    public function cdf($x, int $scale = 10): ImmutableDecimal
50
    {
51
52
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
53
        $one = Numbers::makeOne();
54
55
        if (!$x->isPositive()) {
56
            throw new IntegrityConstraint(
57
                'X must be positive',
58
                'Provide a positive x',
59
                'Exponential distributions work on time to occurrence; the time to occurrence (x) must be positive'
60
            );
61
        }
62
63
        $internalScale = $scale + 2;
64
65
        $e = Numbers::makeE($internalScale);
66
67
        /** @var ImmutableDecimal $cdf */
68
        $cdf =
69
            $one->subtract(
70
                $e->pow(
71
                    $x->multiply($this->lambda)
72
                        ->multiply(-1)
73
                )
74
            )->truncateToScale($scale);
75
76
        return $cdf;
77
78
    }
79
80
    /**
81
     * @param $x
82
     *
83
     * @return ImmutableDecimal
84
     * @throws IntegrityConstraint
85
     */
86
    public function pdf($x, int $scale = 10): ImmutableDecimal
87
    {
88
89
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
90
91
        if (!$x->isPositive()) {
92
            throw new IntegrityConstraint(
93
                'X must be positive',
94
                'Provide a positive x',
95
                'Exponential distributions work on time to occurrence; the time to occurrence (x) must be positive'
96
            );
97
        }
98
99
        $internalScale = $scale + 2;
100
101
        $e = Numbers::makeE($internalScale);
102
103
        /** @var ImmutableDecimal $pdf */
104
        $pdf =
105
            $this->lambda
106
                ->multiply(
107
                    $e->pow(
108
                        $this->lambda
109
                            ->multiply(-1)
110
                            ->multiply($x)
111
                    )
112
                )->truncateToScale($scale);
113
114
        return $pdf;
115
116
    }
117
118
    /**
119
     * @param $x1
120
     * @param $x2
121
     *
122
     * @return ImmutableDecimal
123
     * @throws IntegrityConstraint
124
     */
125
    public function rangePdf($x1, $x2, int $scale = 10): ImmutableDecimal
126
    {
127
        $x1 = Numbers::makeOrDont(Numbers::IMMUTABLE, $x1);
128
        $x2 = Numbers::makeOrDont(Numbers::IMMUTABLE, $x2);
129
130
        if (!$x1->isPositive() || !$x2->isPositive()) {
131
            throw new IntegrityConstraint(
132
                'X must be positive',
133
                'Provide a positive x',
134
                'Exponential distributions work on time to occurrence; the time to occurrence (x) must be positive'
135
            );
136
        }
137
138
        $internalScale = $scale + 2;
139
140
        /** @var ImmutableDecimal $rangePdf */
141
        $rangePdf =
142
            $this->pdf(
143
                $x2,
144
                $internalScale
145
            )->subtract(
146
                $this->pdf(
147
                    $x1,
148
                    $internalScale)
149
            )->abs()
150
            ->truncateToScale($scale);
151
152
        return $rangePdf;
153
    }
154
155
    /**
156
     * @return ImmutableDecimal
157
     *
158
     * @codeCoverageIgnore
159
     */
160
    public function random(): ImmutableDecimal
161
    {
162
163
        $u = RandomProvider::randomDecimal(10);
164
        $one = Numbers::makeOne();
165
166
        /** @var ImmutableDecimal $random */
167
        $random =
168
            $one->subtract($u)
169
                ->ln()
170
                ->divide(
171
                    $this->lambda
172
                        ->multiply(-1)
173
                );
174
175
        return $random;
176
177
    }
178
179
    /**
180
     * @param int|float|DecimalInterface $min
181
     * @param int|float|DecimalInterface $max
182
     * @param int $maxIterations
183
     *
184
     * @return ImmutableDecimal
185
     * @throws OptionalExit
186
     *
187
     * @codeCoverageIgnore
188
     */
189
    public function rangeRandom($min = 0, $max = PHP_INT_MAX, int $maxIterations = 20): ImmutableDecimal
190
    {
191
192
        $i = 0;
193
194
        do {
195
            $randomNumber = $this->random();
196
            $i++;
197
        } while (($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) && $i < $maxIterations);
198
199
        if ($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) {
200
            throw new OptionalExit(
201
                'All random numbers generated were outside of the requested range',
202
                'A suitable random number, restricted by the $max ('.$max.') and $min ('.$min.'), could not be found within '.$maxIterations.' iterations'
203
            );
204
        }
205
206
        return $randomNumber;
207
    }
208
209
}