Normal::zScoreOfX()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 3

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 9
ccs 0
cts 4
cp 0
crap 2
rs 10
c 1
b 0
f 0
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace Samsara\Fermat\Stats\Values\Distribution;
4
5
use ReflectionException;
6
use Samsara\Exceptions\UsageError\IntegrityConstraint;
7
use Samsara\Exceptions\UsageError\OptionalExit;
8
use Samsara\Fermat\Core\Numbers;
9
use Samsara\Fermat\Stats\Types\Distribution;
10
use Samsara\Fermat\Core\Provider\RandomProvider;
11
use Samsara\Fermat\Core\Provider\SequenceProvider;
12
use Samsara\Fermat\Stats\Provider\StatsProvider;
13
use Samsara\Fermat\Core\Types\Base\Interfaces\Numbers\DecimalInterface;
14
use Samsara\Fermat\Expressions\Types\Base\Interfaces\Evaluateables\FunctionInterface;
15
use Samsara\Fermat\Core\Types\Base\Interfaces\Numbers\NumberInterface;
0 ignored issues
show
Bug introduced by
The type Samsara\Fermat\Core\Type...Numbers\NumberInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use Samsara\Fermat\Core\Types\NumberCollection;
17
use Samsara\Fermat\Core\Values\ImmutableDecimal;
18
19
class Normal extends Distribution
20
{
21
22
    /**
23
     * @var ImmutableDecimal
24
     */
25
    private $mean;
26
27
    /**
28
     * @var ImmutableDecimal
29
     */
30
    private $sd;
31
32
    /**
33
     * Normal constructor.
34
     *
35
     * @param int|float|DecimalInterface $mean
36
     * @param int|float|DecimalInterface $sd
37
     *
38
     * @throws IntegrityConstraint
39
     * @throws ReflectionException
40
     */
41 2
    public function __construct($mean, $sd)
42
    {
43
        /** @var ImmutableDecimal $mean */
44 2
        $mean = Numbers::makeOrDont(Numbers::IMMUTABLE, $mean);
45
        /** @var ImmutableDecimal $sd   */
46 2
        $sd = Numbers::makeOrDont(Numbers::IMMUTABLE, $sd);
47
48 2
        $this->mean = $mean;
49 2
        $this->sd = $sd;
50
    }
51
52
    /**
53
     * @return ImmutableDecimal
54
     */
55
    public function getSD(): ImmutableDecimal
56
    {
57
        return $this->sd;
58
    }
59
60
    /**
61
     * @return ImmutableDecimal
62
     */
63 2
    public function getMean(): ImmutableDecimal
64
    {
65 2
        return $this->mean;
66
    }
67
68
    /**
69
     * @param $x
70
     *
71
     * @return ImmutableDecimal
72
     * @throws IntegrityConstraint
73
     * @throws ReflectionException
74
     */
75
    public function evaluateAt($x, int $scale = 10): ImmutableDecimal
76
    {
77
78
        $one = Numbers::makeOne($scale);
79
        $twoPi = Numbers::make2Pi($scale);
80
        $e = Numbers::makeE($scale);
81
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
82
83
        $internalScale = (new NumberCollection([$scale, $x]))->selectScale() + 2;
84
85
        // $left = 1 / ( sqrt(2pi * SD^2) )
86
        $left =
87
            $one->divide(
88
                $twoPi->multiply(
89
                    $this->getSD()
90
                        ->pow(2)
91
                )->sqrt($internalScale),
92
                $internalScale
93
            );
94
        // $right = e^( -1*((x - SD)^2)/(2*SD^2) )
95
        $right =
96
            $e->pow(
97
                $x->subtract(
98
                    $this->getMean()
99
                )->pow(2)
100
                ->divide(
101
                    $this->getSD()
102
                        ->pow(2)
103
                        ->multiply(2),
104
                    $internalScale
105
                )->multiply(-1)
106
            );
107
108
        // Return value is not inlined to ensure proper return type for IDE
109
        /** @var ImmutableDecimal $value */
110
        $value = $left->multiply($right)->truncateToScale($scale);
111
112
        return $value;
113
114
    }
115
116
    /**
117
     * @param int|float|DecimalInterface $p
118
     * @param int|float|DecimalInterface $x
119
     * @param int|float|DecimalInterface $mean
120
     *
121
     * @return Normal
122
     * @throws IntegrityConstraint
123
     * @throws OptionalExit
124
     * @throws ReflectionException
125
     */
126
    public static function makeFromMean($p, $x, $mean): Normal
127
    {
128
        $one = Numbers::makeOne(10);
129
        $p = Numbers::makeOrDont(Numbers::IMMUTABLE, $p);
130
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
131
        $mean = Numbers::makeOrDont(Numbers::IMMUTABLE, $mean);
132
133
        $internalScale = (new NumberCollection([$one, $p, $x, $mean]))->selectScale();
134
135
        $internalScale += 2;
136
137
        $z = StatsProvider::inverseNormalCDF($one->subtract($p), $internalScale);
138
139
        $sd =
140
            $x->subtract($mean)
141
                ->divide(
142
                    $z,
143
                    $internalScale
144
                )->abs()
145
                ->truncateToScale($internalScale-2);
146
147
        return new Normal($mean, $sd);
148
    }
149
150
    /**
151
     * @param int|float|DecimalInterface $p
152
     * @param int|float|DecimalInterface $x
153
     * @param int|float|DecimalInterface $sd
154
     *
155
     * @return Normal
156
     * @throws IntegrityConstraint
157
     * @throws OptionalExit
158
     * @throws ReflectionException
159
     */
160
    public static function makeFromSd($p, $x, $sd): Normal
161
    {
162
        $one = Numbers::makeOne(10);
163
        $p = Numbers::makeOrDont(Numbers::IMMUTABLE, $p);
164
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
165
        $sd = Numbers::makeOrDont(Numbers::IMMUTABLE, $sd);
166
167
        $internalScale = (new NumberCollection([$one, $p, $x, $sd]))->selectScale();
168
169
        $internalScale += 2;
170
171
        $z = StatsProvider::inverseNormalCDF($one->subtract($p), $internalScale);
172
173
        $mean =
174
            $x->add(
175
                $z->multiply($sd)
176
            )->truncateToScale($internalScale-2);
177
178
        return new Normal($mean, $sd);
179
    }
180
181
    /**
182
     * @param int|float|DecimalInterface $x
183
     *
184
     * @return ImmutableDecimal
185
     * @throws IntegrityConstraint
186
     * @throws OptionalExit
187
     * @throws ReflectionException
188
     */
189 2
    public function cdf($x): ImmutableDecimal
190
    {
191 2
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
192
193 2
        $oneHalf = Numbers::make(Numbers::IMMUTABLE, '0.5');
194 2
        $one = Numbers::makeOne();
195 2
        $sqrtTwo = Numbers::make(Numbers::IMMUTABLE, 2)->sqrt();
196
197
        /** @var ImmutableDecimal $cdf */
198 2
        $cdf =
199 2
            $oneHalf->multiply(
200 2
                $one->add(
201 2
                    StatsProvider::gaussErrorFunction(
202 2
                        $x->subtract($this->mean)
203 2
                            ->divide(
204 2
                                $this->sd->multiply($sqrtTwo)
205
                            )
206
                    )
207
                )
208
            );
209
210 2
        return $cdf;
211
    }
212
213
    /**
214
     * @param int|float|DecimalInterface $x1
215
     * @param null|int|float|DecimalInterface $x2
216
     *
217
     * @return ImmutableDecimal
218
     * @throws IntegrityConstraint
219
     * @throws ReflectionException
220
     */
221
    public function pdf($x1, $x2 = null): ImmutableDecimal
222
    {
223
224
        $x1 = Numbers::makeOrDont(Numbers::IMMUTABLE, $x1);
225
226
        if (is_null($x2)) {
227
            $separation = $x1->subtract($this->mean)->multiply(2)->abs();
228
229
            if ($this->mean->isLessThan($x1)) {
230
                $x2 = $x1->subtract($separation);
231
            } else {
232
                $x2 = $x1->add($separation);
233
            }
234
        }
235
236
        /** @var ImmutableDecimal $pdf */
237
        $pdf =
238
            $this->cdf($x1)
239
                ->subtract(
240
                    $this->cdf($x2)
241
                )->abs();
242
243
        return $pdf;
244
    }
245
246
    /**
247
     * @param FunctionInterface $function
248
     * @param $x
249
     * @return ImmutableDecimal
250
     * @throws IntegrityConstraint
251
     * @throws ReflectionException
252
     */
253
    public function cdfProduct(FunctionInterface $function, $x): ImmutableDecimal
254
    {
255
256
        $loop = 0;
257
258
        $cdf = Numbers::makeZero();
259
260
        while (true) {
261
            if (count($function->describeShape()) === 0) {
262
                break;
263
            }
264
265
            $cdf = $cdf->add($function->evaluateAt($x)->multiply(SequenceProvider::nthPowerNegativeOne($loop)));
266
267
            $function = $function->derivativeExpression();
268
        }
269
270
        /** @var ImmutableDecimal $cdf */
271
        $cdf = $cdf->multiply($this->cdf($x));
272
273
        return $cdf;
274
275
    }
276
277
    /**
278
     * @param FunctionInterface $function
279
     * @param $x1
280
     * @param $x2
281
     *
282
     * @return ImmutableDecimal
283
     * @throws IntegrityConstraint
284
     * @throws ReflectionException
285
     */
286
    public function pdfProduct(FunctionInterface $function, $x1, $x2): ImmutableDecimal
287
    {
288
        /** @var ImmutableDecimal $pdf */
289
        $pdf = $this->cdfProduct($function, $x2)->subtract($this->cdfProduct($function, $x1));
290
291
        return $pdf;
292
    }
293
294
    /**
295
     * @param int|float|DecimalInterface $x
296
     *
297
     * @return ImmutableDecimal
298
     * @throws IntegrityConstraint
299
     */
300
    public function percentBelowX($x): ImmutableDecimal
301
    {
302
        return $this->cdf($x);
303
    }
304
305
    /**
306
     * @param int|float|DecimalInterface $x
307
     *
308
     * @return ImmutableDecimal
309
     * @throws IntegrityConstraint
310
     * @throws ReflectionException
311
     */
312 2
    public function percentAboveX($x): ImmutableDecimal
313
    {
314 2
        $one = Numbers::makeOne();
315
316
        /** @var ImmutableDecimal $perc */
317 2
        $perc = $one->subtract($this->cdf($x));
318
319 2
        return $perc;
320
    }
321
322
    /**
323
     * @param int|float|DecimalInterface $x
324
     *
325
     * @return ImmutableDecimal
326
     * @throws IntegrityConstraint
327
     * @throws ReflectionException
328
     */
329
    public function zScoreOfX($x): ImmutableDecimal
330
    {
331
        /** @var ImmutableDecimal $x */
332
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
333
334
        /** @var ImmutableDecimal $z */
335
        $z = $x->subtract($this->mean)->divide($this->sd);
336
337
        return $z;
338
    }
339
340
    /**
341
     * @param int|float|DecimalInterface $z
342
     *
343
     * @return ImmutableDecimal
344
     * @throws IntegrityConstraint
345
     * @throws ReflectionException
346
     */
347
    public function xFromZScore($z): ImmutableDecimal
348
    {
349
        $z = Numbers::makeOrDont(Numbers::IMMUTABLE, $z);
350
351
        /** @var ImmutableDecimal $x */
352
        $x = $z->multiply($this->sd)->add($this->mean);
353
354
        return $x;
355
    }
356
357
    /**
358
     * @return ImmutableDecimal
359
     * @throws IntegrityConstraint
360
     * @throws ReflectionException
361
     *
362
     * @codeCoverageIgnore
363
     */
364
    public function random(): ImmutableDecimal
365
    {
366
        $rand1 = RandomProvider::randomDecimal(20);
367
        $rand2 = RandomProvider::randomDecimal(20);
368
369
        $randomNumber =
370
            $rand1->ln()
371
                ->multiply(-2)
372
                ->sqrt()
373
                ->multiply(
374
                    $rand2->multiply(Numbers::TAU)
375
                        ->cos(1, false)
376
                );
377
        /** @var ImmutableDecimal $randomNumber */
378
        $randomNumber = $randomNumber->multiply($this->sd)->add($this->mean);
379
380
        return $randomNumber;
381
    }
382
383
    /**
384
     * @param int|float|NumberInterface $min
385
     * @param int|float|NumberInterface $max
386
     * @param int $maxIterations
387
     *
388
     * @return ImmutableDecimal
389
     * @throws OptionalExit
390
     * @throws IntegrityConstraint
391
     * @throws ReflectionException
392
     *
393
     * @codeCoverageIgnore
394
     */
395
    public function rangeRandom($min = 0, $max = PHP_INT_MAX, int $maxIterations = 20): ImmutableDecimal
396
    {
397
        $i = 0;
398
399
        do {
400
            $randomNumber = $this->random();
401
            $i++;
402
        } while (($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) && $i < $maxIterations);
403
404
        if ($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) {
405
            throw new OptionalExit(
406
                'All random numbers generated were outside of the requested range',
407
                'Widen the acceptable range of random values, or allow the function to perform mor iterations',
408
                'A suitable random number, restricted by the $max ('.$max.') and $min ('.$min.'), could not be found within '.$maxIterations.' iterations'
409
            );
410
        }
411
412
        return $randomNumber;
413
    }
414
415
}