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