Passed
Push — master ( a639aa...cb526f )
by Jordan
02:41
created

Normal::makeFromSd()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 16
ccs 10
cts 10
cp 1
rs 9.9666
cc 1
nc 1
nop 3
crap 1
1
<?php
2
3
namespace Samsara\Fermat\Values\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\Types\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;
0 ignored issues
show
Bug introduced by
The type Samsara\Fermat\Types\Bas...ables\FunctionInterface 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...
15
use Samsara\Fermat\Types\Base\Interfaces\Numbers\NumberInterface;
16
use Samsara\Fermat\Types\NumberCollection;
17
use Samsara\Fermat\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 6
    public function __construct($mean, $sd)
42
    {
43
        /** @var ImmutableDecimal $mean */
44 6
        $mean = Numbers::makeOrDont(Numbers::IMMUTABLE, $mean);
45
        /** @var ImmutableDecimal $sd   */
46 6
        $sd = Numbers::makeOrDont(Numbers::IMMUTABLE, $sd);
47
48 6
        $this->mean = $mean;
49 6
        $this->sd = $sd;
50 6
    }
51
52
    /**
53
     * @return ImmutableDecimal
54
     */
55 2
    public function getSD(): ImmutableDecimal
56
    {
57 2
        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 1
    public function evaluateAt($x, int $scale = 10): ImmutableDecimal
76
    {
77
78 1
        $one = Numbers::makeOne($scale);
79 1
        $twoPi = Numbers::make2Pi($scale);
80 1
        $e = Numbers::makeE($scale);
81 1
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
82
83 1
        $internalScale = (new NumberCollection([$scale, $x]))->selectScale() + 2;
84
85
        // $left = 1 / ( sqrt(2pi * SD^2) )
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
86 1
        $left = $one->divide($twoPi->multiply($this->getSD()->pow(2))->sqrt($internalScale), $internalScale);
87
        // $right = e^( -1*((x - SD)^2)/(2*SD^2) )
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
88 1
        $right = $e->pow($x->subtract($this->getMean())->pow(2)->divide($this->getSD()->pow(2)->multiply(2), $internalScale)->multiply(-1));
89
90
        // Return value is not inlined to ensure proper return type for IDE
91
        /** @var ImmutableDecimal $value */
92 1
        $value = $left->multiply($right)->truncateToScale($scale);
93
94 1
        return $value;
95
96
    }
97
98
    /**
99
     * @param int|float|DecimalInterface $p
100
     * @param int|float|DecimalInterface $x
101
     * @param int|float|DecimalInterface $mean
102
     *
103
     * @return Normal
104
     * @throws IntegrityConstraint
105
     * @throws OptionalExit
106
     * @throws ReflectionException
107
     */
108 1
    public static function makeFromMean($p, $x, $mean): Normal
109
    {
110 1
        $one = Numbers::makeOne(10);
111 1
        $p = Numbers::makeOrDont(Numbers::IMMUTABLE, $p);
112 1
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
113 1
        $mean = Numbers::makeOrDont(Numbers::IMMUTABLE, $mean);
114
115 1
        $internalScale = (new NumberCollection([$one, $p, $x, $mean]))->selectScale();
116
117 1
        $internalScale += 2;
118
119 1
        $z = StatsProvider::inverseNormalCDF($one->subtract($p), $internalScale);
120
121 1
        $sd = $x->subtract($mean)->divide($z, $internalScale)->abs()->truncateToScale($internalScale-2);
0 ignored issues
show
Bug introduced by
The method truncateToScale() 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

121
        $sd = $x->subtract($mean)->divide($z, $internalScale)->abs()->/** @scrutinizer ignore-call */ truncateToScale($internalScale-2);
Loading history...
Bug introduced by
The method truncateToScale() 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

121
        $sd = $x->subtract($mean)->divide($z, $internalScale)->abs()->/** @scrutinizer ignore-call */ truncateToScale($internalScale-2);

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...
122
123 1
        return new Normal($mean, $sd);
124
    }
125
126
    /**
127
     * @param int|float|DecimalInterface $p
128
     * @param int|float|DecimalInterface $x
129
     * @param int|float|DecimalInterface $sd
130
     *
131
     * @return Normal
132
     * @throws IntegrityConstraint
133
     * @throws OptionalExit
134
     * @throws ReflectionException
135
     */
136 1
    public static function makeFromSd($p, $x, $sd): Normal
137
    {
138 1
        $one = Numbers::makeOne(10);
139 1
        $p = Numbers::makeOrDont(Numbers::IMMUTABLE, $p);
140 1
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
141 1
        $sd = Numbers::makeOrDont(Numbers::IMMUTABLE, $sd);
142
143 1
        $internalScale = (new NumberCollection([$one, $p, $x, $sd]))->selectScale();
144
145 1
        $internalScale += 2;
146
147 1
        $z = StatsProvider::inverseNormalCDF($one->subtract($p), $internalScale);
148
149 1
        $mean = $x->add($z->multiply($sd))->truncateToScale($internalScale-2);
150
151 1
        return new Normal($mean, $sd);
152
    }
153
154
    /**
155
     * @param int|float|DecimalInterface $x
156
     *
157
     * @return ImmutableDecimal
158
     * @throws IntegrityConstraint
159
     * @throws OptionalExit
160
     * @throws ReflectionException
161
     */
162 4
    public function cdf($x): ImmutableDecimal
163
    {
164 4
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
165
166 4
        $oneHalf = Numbers::make(Numbers::IMMUTABLE, '0.5');
167 4
        $one = Numbers::makeOne();
168 4
        $sqrtTwo = Numbers::make(Numbers::IMMUTABLE, 2)->sqrt();
169
170
        /** @var ImmutableDecimal $cdf */
171 4
        $cdf = $oneHalf->multiply($one->add(StatsProvider::gaussErrorFunction(
172 4
            $x->subtract($this->mean)->divide($this->sd->multiply($sqrtTwo))
173
        )));
174
175 4
        return $cdf;
176
    }
177
178
    /**
179
     * @param int|float|DecimalInterface $x1
180
     * @param null|int|float|DecimalInterface $x2
181
     *
182
     * @return ImmutableDecimal
183
     * @throws IntegrityConstraint
184
     * @throws ReflectionException
185
     */
186 3
    public function pdf($x1, $x2 = null): ImmutableDecimal
187
    {
188
189 3
        $x1 = Numbers::makeOrDont(Numbers::IMMUTABLE, $x1);
190
191 3
        if (is_null($x2)) {
192 2
            $separation = $x1->subtract($this->mean)->multiply(2)->abs();
193
194 2
            if ($this->mean->isLessThan($x1)) {
195 1
                $x2 = $x1->subtract($separation);
196
            } else {
197 2
                $x2 = $x1->add($separation);
198
            }
199
        }
200
201
        /** @var ImmutableDecimal $pdf */
202 3
        $pdf = $this->cdf($x1)->subtract($this->cdf($x2))->abs();
203
204 3
        return $pdf;
205
    }
206
207
    /**
208
     * @param FunctionInterface $function
209
     * @param $x
210
     * @return ImmutableDecimal
211
     * @throws IntegrityConstraint
212
     * @throws ReflectionException
213
     */
214
    public function cdfProduct(FunctionInterface $function, $x): ImmutableDecimal
215
    {
216
217
        $loop = 0;
218
219
        $cdf = Numbers::makeZero();
220
221
        while (true) {
222
            if (count($function->describeShape()) === 0) {
223
                break;
224
            }
225
226
            $cdf = $cdf->add($function->evaluateAt($x)->multiply(SequenceProvider::nthPowerNegativeOne($loop)));
227
228
            $function = $function->derivativeExpression();
229
        }
230
231
        /** @var ImmutableDecimal $cdf */
232
        $cdf = $cdf->multiply($this->cdf($x));
233
234
        return $cdf;
235
236
    }
237
238
    /**
239
     * @param FunctionInterface $function
240
     * @param $x1
241
     * @param $x2
242
     *
243
     * @return ImmutableDecimal
244
     * @throws IntegrityConstraint
245
     * @throws ReflectionException
246
     */
247
    public function pdfProduct(FunctionInterface $function, $x1, $x2): ImmutableDecimal
248
    {
249
        /** @var ImmutableDecimal $pdf */
250
        $pdf = $this->cdfProduct($function, $x2)->subtract($this->cdfProduct($function, $x1));
251
252
        return $pdf;
253
    }
254
255
    /**
256
     * @param int|float|DecimalInterface $x
257
     *
258
     * @return ImmutableDecimal
259
     * @throws IntegrityConstraint
260
     */
261 1
    public function percentBelowX($x): ImmutableDecimal
262
    {
263 1
        return $this->cdf($x);
264
    }
265
266
    /**
267
     * @param int|float|DecimalInterface $x
268
     *
269
     * @return ImmutableDecimal
270
     * @throws IntegrityConstraint
271
     * @throws ReflectionException
272
     */
273 1
    public function percentAboveX($x): ImmutableDecimal
274
    {
275 1
        $one = Numbers::makeOne();
276
277
        /** @var ImmutableDecimal $perc */
278 1
        $perc = $one->subtract($this->cdf($x));
279
280 1
        return $perc;
281
    }
282
283
    /**
284
     * @param int|float|DecimalInterface $x
285
     *
286
     * @return ImmutableDecimal
287
     * @throws IntegrityConstraint
288
     * @throws ReflectionException
289
     */
290 1
    public function zScoreOfX($x): ImmutableDecimal
291
    {
292
        /** @var ImmutableDecimal $x */
293 1
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
294
295
        /** @var ImmutableDecimal $z */
296 1
        $z = $x->subtract($this->mean)->divide($this->sd);
297
298 1
        return $z;
299
    }
300
301
    /**
302
     * @param int|float|DecimalInterface $z
303
     *
304
     * @return ImmutableDecimal
305
     * @throws IntegrityConstraint
306
     * @throws ReflectionException
307
     */
308 1
    public function xFromZScore($z): ImmutableDecimal
309
    {
310 1
        $z = Numbers::makeOrDont(Numbers::IMMUTABLE, $z);
311
312
        /** @var ImmutableDecimal $x */
313 1
        $x = $z->multiply($this->sd)->add($this->mean);
314
315 1
        return $x;
316
    }
317
318
    /**
319
     * @return ImmutableDecimal
320
     * @throws IntegrityConstraint
321
     * @throws ReflectionException
322
     *
323
     * @codeCoverageIgnore
324
     */
325
    public function random(): ImmutableDecimal
326
    {
327
        $int1 = PolyfillProvider::randomInt(0, PHP_INT_MAX);
328
        $int2 = PolyfillProvider::randomInt(0, PHP_INT_MAX);
329
330
        $rand1 = Numbers::make(Numbers::IMMUTABLE, $int1, 20);
331
        $rand1 = $rand1->divide(PHP_INT_MAX);
332
        $rand2 = Numbers::make(Numbers::IMMUTABLE, $int2, 20);
333
        $rand2 = $rand2->divide(PHP_INT_MAX);
334
335
        $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\Values\ImmutableFraction. ( Ignorable by Annotation )

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

335
        $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...
Bug introduced by
The method ln() does not exist on Samsara\Fermat\Values\MutableFraction. ( Ignorable by Annotation )

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

335
        $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...
336
        /** @var ImmutableDecimal $randomNumber */
337
        $randomNumber = $randomNumber->multiply($this->sd)->add($this->mean);
338
339
        return $randomNumber;
340
    }
341
342
    /**
343
     * @param int|float|NumberInterface $min
344
     * @param int|float|NumberInterface $max
345
     * @param int $maxIterations
346
     *
347
     * @return ImmutableDecimal
348
     * @throws OptionalExit
349
     * @throws IntegrityConstraint
350
     * @throws ReflectionException
351
     *
352
     * @codeCoverageIgnore
353
     */
354
    public function rangeRandom($min = 0, $max = PHP_INT_MAX, int $maxIterations = 20): ImmutableDecimal
355
    {
356
        $i = 0;
357
358
        do {
359
            $randomNumber = $this->random();
360
            $i++;
361
        } while (($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) && $i < $maxIterations);
362
363
        if ($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) {
364
            throw new OptionalExit(
365
                'All random numbers generated were outside of the requested range',
366
                '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 $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

366
                'A suitable random number, restricted by the $max ('.$max.') and $min ('./** @scrutinizer ignore-type */ $min.'), could not be found within '.$maxIterations.' iterations'
Loading history...
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

366
                'A suitable random number, restricted by the $max ('./** @scrutinizer ignore-type */ $max.') and $min ('.$min.'), could not be found within '.$maxIterations.' iterations'
Loading history...
367
            );
368
        }
369
370
        return $randomNumber;
371
    }
372
373
}