Failed Conditions
Pull Request — dev (#50)
by Jordan
09:06 queued 04:26
created

Normal::rangeRandom()   A

Complexity

Conditions 6
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 10
nc 2
nop 3
dl 0
loc 17
ccs 0
cts 10
cp 0
crap 42
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
namespace Samsara\Fermat\Provider\Distribution;
4
5
use ReflectionException;
6
use Samsara\Exceptions\UsageError\IntegrityConstraint;
7
use Samsara\Exceptions\UsageError\OptionalExit;
8
use Samsara\Fermat\Numbers;
9
use Samsara\Fermat\Provider\Distribution\Base\Distribution;
10
use Samsara\Fermat\Provider\PolyfillProvider;
11
use Samsara\Fermat\Provider\SequenceProvider;
12
use Samsara\Fermat\Provider\StatsProvider;
13
use Samsara\Fermat\Types\Base\Interfaces\Numbers\DecimalInterface;
14
use Samsara\Fermat\Types\Base\Interfaces\Evaluateables\FunctionInterface;
15
use Samsara\Fermat\Types\Base\Interfaces\Numbers\NumberInterface;
16
use Samsara\Fermat\Values\ImmutableDecimal;
17
18
class Normal extends Distribution
19
{
20
21
    /**
22
     * @var ImmutableDecimal
23
     */
24
    private $mean;
25
26
    /**
27
     * @var ImmutableDecimal
28
     */
29
    private $sd;
30
31
    /**
32
     * Normal constructor.
33
     *
34
     * @param int|float|DecimalInterface $mean
35
     * @param int|float|DecimalInterface $sd
36
     *
37
     * @throws IntegrityConstraint
38
     * @throws ReflectionException
39
     */
40 5
    public function __construct($mean, $sd)
41
    {
42
        /** @var ImmutableDecimal $mean */
43 5
        $mean = Numbers::makeOrDont(Numbers::IMMUTABLE, $mean);
44
        /** @var ImmutableDecimal $sd   */
45 5
        $sd = Numbers::makeOrDont(Numbers::IMMUTABLE, $sd);
46
47 5
        $this->mean = $mean;
48 5
        $this->sd = $sd;
49 5
    }
50
51
    /**
52
     * @return ImmutableDecimal
53
     */
54
    public function getSD(): ImmutableDecimal
55
    {
56
        return $this->sd;
57
    }
58
59
    /**
60
     * @return ImmutableDecimal
61
     */
62
    public function getMean(): ImmutableDecimal
63
    {
64
        return $this->mean;
65
    }
66
67
    /**
68
     * @param $x
69
     *
70
     * @return ImmutableDecimal
71
     * @throws IntegrityConstraint
72
     * @throws ReflectionException
73
     */
74
    public function evaluateAt($x): ImmutableDecimal
75
    {
76
77
        $one = Numbers::makeOne();
78
        $twoPi = Numbers::make2Pi();
79
        $e = Numbers::makeE();
80
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
81
82
        $left = $one->divide($twoPi->multiply($this->getSD()->pow(2))->sqrt());
83
        $right = $e->pow($x->subtract($this->getMean())->pow(2)->divide($this->getSD()->pow(2)->multiply(2))->multiply(-1));
0 ignored issues
show
Bug introduced by
The method divide() does not exist on Samsara\Fermat\Values\Ge...ems\CartesianCoordinate. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

83
        $right = $e->pow($x->subtract($this->getMean())->pow(2)->/** @scrutinizer ignore-call */ divide($this->getSD()->pow(2)->multiply(2))->multiply(-1));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method divide() does not exist on Samsara\Fermat\Types\Bas...tes\CoordinateInterface. It seems like you code against a sub-type of Samsara\Fermat\Types\Bas...tes\CoordinateInterface such as Samsara\Fermat\Types\ComplexNumber or Samsara\Fermat\Types\ComplexNumber. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

83
        $right = $e->pow($x->subtract($this->getMean())->pow(2)->/** @scrutinizer ignore-call */ divide($this->getSD()->pow(2)->multiply(2))->multiply(-1));
Loading history...
Bug introduced by
The method multiply() does not exist on Samsara\Fermat\Values\Ge...ems\CartesianCoordinate. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

83
        $right = $e->pow($x->subtract($this->getMean())->pow(2)->divide($this->getSD()->pow(2)->multiply(2))->/** @scrutinizer ignore-call */ multiply(-1));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method multiply() does not exist on Samsara\Fermat\Types\Bas...tes\CoordinateInterface. It seems like you code against a sub-type of Samsara\Fermat\Types\Bas...tes\CoordinateInterface such as Samsara\Fermat\Types\ComplexNumber or Samsara\Fermat\Types\ComplexNumber. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

83
        $right = $e->pow($x->subtract($this->getMean())->pow(2)->divide($this->getSD()->pow(2)->multiply(2))->/** @scrutinizer ignore-call */ multiply(-1));
Loading history...
84
85
        /** @var ImmutableDecimal $value */
86
        $value = $left->multiply($right);
87
88
        return $value;
89
90
    }
91
92
    /**
93
     * @param int|float|DecimalInterface $p
94
     * @param int|float|DecimalInterface $x
95
     * @param int|float|DecimalInterface $mean
96
     *
97
     * @return Normal
98
     * @throws IntegrityConstraint
99
     * @throws OptionalExit
100
     * @throws ReflectionException
101
     */
102
    public static function makeFromMean($p, $x, $mean): Normal
103
    {
104
        $one = Numbers::makeOne();
105
        $p = Numbers::makeOrDont(Numbers::IMMUTABLE, $p);
106
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
107
        $mean = Numbers::makeOrDont(Numbers::IMMUTABLE, $mean);
108
109
        $z = StatsProvider::inverseNormalCDF($one->subtract($p));
110
111
        $sd = $x->subtract($mean)->divide($z);
112
113
        return new Normal($mean, $sd);
0 ignored issues
show
Bug introduced by
It seems like $sd can also be of type Samsara\Fermat\Types\Bas...mbers\FractionInterface and Samsara\Fermat\Values\ImmutableComplexNumber; however, parameter $sd of Samsara\Fermat\Provider\...n\Normal::__construct() does only seem to accept Samsara\Fermat\Types\Bas...nterface|double|integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

113
        return new Normal($mean, /** @scrutinizer ignore-type */ $sd);
Loading history...
114
    }
115
116
    /**
117
     * @param int|float|DecimalInterface $p
118
     * @param int|float|DecimalInterface $x
119
     * @param int|float|DecimalInterface $sd
120
     *
121
     * @return Normal
122
     * @throws IntegrityConstraint
123
     * @throws OptionalExit
124
     * @throws ReflectionException
125
     */
126
    public static function makeFromSd($p, $x, $sd): Normal
127
    {
128
        $one = Numbers::makeOne();
129
        $p = Numbers::makeOrDont(Numbers::IMMUTABLE, $p);
130
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
131
        $sd = Numbers::makeOrDont(Numbers::IMMUTABLE, $sd);
132
133
        $z = StatsProvider::inverseNormalCDF($one->subtract($p));
134
135
        $mean = $x->subtract($z->multiply($sd));
136
137
        return new Normal($mean, $sd);
138
    }
139
140
    /**
141
     * @param int|float|DecimalInterface $x
142
     *
143
     * @return ImmutableDecimal
144
     * @throws IntegrityConstraint
145
     * @throws OptionalExit
146
     * @throws ReflectionException
147
     */
148 4
    public function cdf($x): ImmutableDecimal
149
    {
150 4
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
151
152 4
        $oneHalf = Numbers::make(Numbers::IMMUTABLE, '0.5');
153 4
        $one = Numbers::makeOne();
154 4
        $sqrtTwo = Numbers::make(Numbers::IMMUTABLE, 2)->sqrt();
155
156
        /** @var ImmutableDecimal $cdf */
157 4
        $cdf = $oneHalf->multiply($one->add(StatsProvider::gaussErrorFunction(
158 4
            $x->subtract($this->mean)->divide($this->sd->multiply($sqrtTwo))
159
        )));
160
161 4
        return $cdf;
162
    }
163
164
    /**
165
     * @param int|float|DecimalInterface $x1
166
     * @param null|int|float|DecimalInterface $x2
167
     *
168
     * @return ImmutableDecimal
169
     * @throws IntegrityConstraint
170
     * @throws ReflectionException
171
     */
172 2
    public function pdf($x1, $x2 = null): ImmutableDecimal
173
    {
174
175 2
        $x1 = Numbers::makeOrDont(Numbers::IMMUTABLE, $x1);
176
177 2
        if (is_null($x2)) {
178 1
            $separation = $x1->subtract($this->mean)->multiply(2)->abs();
179
180 1
            if ($this->mean->isLessThan($x1)) {
181 1
                $x2 = $x1->subtract($separation);
182
            } else {
183 1
                $x2 = $x1->add($separation);
184
            }
185
        }
186
187
        /** @var ImmutableDecimal $pdf */
188 2
        $pdf = $this->cdf($x1)->subtract($this->cdf($x2))->abs();
189
190 2
        return $pdf;
191
    }
192
193
    /**
194
     * @param FunctionInterface $function
195
     * @param $x
196
     * @return ImmutableDecimal
197
     * @throws IntegrityConstraint
198
     * @throws ReflectionException
199
     */
200
    public function cdfProduct(FunctionInterface $function, $x): ImmutableDecimal
201
    {
202
203
        $loop = 0;
204
205
        $cdf = Numbers::makeZero();
206
207
        while (true) {
208
            if (count($function->describeShape()) === 0) {
209
                break;
210
            }
211
212
            $cdf = $cdf->add($function->evaluateAt($x)->multiply(SequenceProvider::nthPowerNegativeOne($loop)));
213
214
            $function = $function->derivativeExpression();
215
        }
216
217
        /** @var ImmutableDecimal $cdf */
218
        $cdf = $cdf->multiply($this->cdf($x));
219
220
        return $cdf;
221
222
    }
223
224
    /**
225
     * @param FunctionInterface $function
226
     * @param $x1
227
     * @param $x2
228
     *
229
     * @return ImmutableDecimal
230
     * @throws IntegrityConstraint
231
     * @throws ReflectionException
232
     */
233
    public function pdfProduct(FunctionInterface $function, $x1, $x2): ImmutableDecimal
234
    {
235
        /** @var ImmutableDecimal $pdf */
236
        $pdf = $this->cdfProduct($function, $x2)->subtract($this->cdfProduct($function, $x1));
237
238
        return $pdf;
239
    }
240
241
    /**
242
     * @param int|float|DecimalInterface $x
243
     *
244
     * @return ImmutableDecimal
245
     * @throws IntegrityConstraint
246
     */
247 1
    public function percentBelowX($x): ImmutableDecimal
248
    {
249 1
        return $this->cdf($x);
250
    }
251
252
    /**
253
     * @param int|float|DecimalInterface $x
254
     *
255
     * @return ImmutableDecimal
256
     * @throws IntegrityConstraint
257
     * @throws ReflectionException
258
     */
259 2
    public function percentAboveX($x): ImmutableDecimal
260
    {
261 2
        $one = Numbers::makeOne();
262
263
        /** @var ImmutableDecimal $perc */
264 2
        $perc = $one->subtract($this->cdf($x));
265
266 2
        return $perc;
267
    }
268
269
    /**
270
     * @param int|float|DecimalInterface $x
271
     *
272
     * @return ImmutableDecimal
273
     * @throws IntegrityConstraint
274
     * @throws ReflectionException
275
     */
276 1
    public function zScoreOfX($x): ImmutableDecimal
277
    {
278
        /** @var ImmutableDecimal $x */
279 1
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
280
281
        /** @var ImmutableDecimal $z */
282 1
        $z = $x->subtract($this->mean)->divide($this->sd);
283
284 1
        return $z;
285
    }
286
287
    /**
288
     * @param int|float|DecimalInterface $z
289
     *
290
     * @return ImmutableDecimal
291
     * @throws IntegrityConstraint
292
     * @throws ReflectionException
293
     */
294 1
    public function xFromZScore($z): ImmutableDecimal
295
    {
296 1
        $z = Numbers::makeOrDont(Numbers::IMMUTABLE, $z);
297
298
        /** @var ImmutableDecimal $x */
299 1
        $x = $z->multiply($this->sd)->add($this->mean);
300
301 1
        return $x;
302
    }
303
304
    /**
305
     * @return ImmutableDecimal
306
     * @throws IntegrityConstraint
307
     * @throws ReflectionException
308
     */
309
    public function random(): ImmutableDecimal
310
    {
311
        $int1 = PolyfillProvider::randomInt(0, PHP_INT_MAX);
312
        $int2 = PolyfillProvider::randomInt(0, PHP_INT_MAX);
313
314
        $rand1 = Numbers::make(Numbers::IMMUTABLE, $int1, 20);
315
        $rand1 = $rand1->divide(PHP_INT_MAX);
316
        $rand2 = Numbers::make(Numbers::IMMUTABLE, $int2, 20);
317
        $rand2 = $rand2->divide(PHP_INT_MAX);
318
319
        $randomNumber = $rand1->ln()->multiply(-2)->sqrt()->multiply($rand2->multiply(Numbers::TAU)->cos(1, false));
0 ignored issues
show
Bug introduced by
The method ln() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. It seems like you code against a sub-type of Samsara\Fermat\Types\Bas...Numbers\NumberInterface such as Samsara\Fermat\Types\Bas...umbers\DecimalInterface or Samsara\Fermat\Types\Decimal. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

319
        $randomNumber = $rand1->/** @scrutinizer ignore-call */ ln()->multiply(-2)->sqrt()->multiply($rand2->multiply(Numbers::TAU)->cos(1, false));
Loading history...
Bug introduced by
The method cos() does not exist on Samsara\Fermat\Types\Bas...mbers\FractionInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

319
        $randomNumber = $rand1->ln()->multiply(-2)->sqrt()->multiply($rand2->multiply(Numbers::TAU)->/** @scrutinizer ignore-call */ cos(1, false));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method cos() does not exist on Samsara\Fermat\Types\Bas...Numbers\NumberInterface. It seems like you code against a sub-type of Samsara\Fermat\Types\Bas...Numbers\NumberInterface such as Samsara\Fermat\Types\Bas...umbers\DecimalInterface or Samsara\Fermat\Types\Decimal. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

319
        $randomNumber = $rand1->ln()->multiply(-2)->sqrt()->multiply($rand2->multiply(Numbers::TAU)->/** @scrutinizer ignore-call */ cos(1, false));
Loading history...
Bug introduced by
The method ln() does not exist on Samsara\Fermat\Types\Bas...mbers\FractionInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

319
        $randomNumber = $rand1->/** @scrutinizer ignore-call */ ln()->multiply(-2)->sqrt()->multiply($rand2->multiply(Numbers::TAU)->cos(1, false));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
320
        /** @var ImmutableDecimal $randomNumber */
321
        $randomNumber = $randomNumber->multiply($this->sd)->add($this->mean);
322
323
        return $randomNumber;
324
    }
325
326
    /**
327
     * @param int|float|NumberInterface $min
328
     * @param int|float|NumberInterface $max
329
     * @param int $maxIterations
330
     *
331
     * @return ImmutableDecimal
332
     * @throws OptionalExit
333
     * @throws IntegrityConstraint
334
     * @throws ReflectionException
335
     */
336
    public function rangeRandom($min = 0, $max = PHP_INT_MAX, int $maxIterations = 20): ImmutableDecimal
337
    {
338
        $i = 0;
339
340
        do {
341
            $randomNumber = $this->random();
342
            $i++;
343
        } while (($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) && $i < $maxIterations);
344
345
        if ($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) {
346
            throw new OptionalExit(
347
                'All random numbers generated were outside of the requested range',
348
                'A suitable random number, restricted by the $max ('.$max.') and $min ('.$min.'), could not be found within '.$maxIterations.' iterations'
0 ignored issues
show
Bug introduced by
Are you sure $max of type Samsara\Fermat\Types\Bas...nterface|double|integer can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

348
                'A suitable random number, restricted by the $max ('./** @scrutinizer ignore-type */ $max.') and $min ('.$min.'), could not be found within '.$maxIterations.' iterations'
Loading history...
Bug introduced by
Are you sure $min of type Samsara\Fermat\Types\Bas...nterface|double|integer can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

348
                'A suitable random number, restricted by the $max ('.$max.') and $min ('./** @scrutinizer ignore-type */ $min.'), could not be found within '.$maxIterations.' iterations'
Loading history...
349
            );
350
        }
351
352
        return $randomNumber;
353
    }
354
355
}