Test Failed
Pull Request — master (#47)
by Jordan
14:20
created

Normal::getSD()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
1
<?php
2
3
namespace Samsara\Fermat\Provider\Distribution;
4
5
use Samsara\Exceptions\UsageError\IntegrityConstraint;
6
use Samsara\Exceptions\UsageError\OptionalExit;
7
use Samsara\Fermat\Numbers;
8
use Samsara\Fermat\Provider\Distribution\Base\Distribution;
9
use Samsara\Fermat\Provider\PolyfillProvider;
10
use Samsara\Fermat\Provider\SequenceProvider;
11
use Samsara\Fermat\Provider\StatsProvider;
12
use Samsara\Fermat\Types\Base\Interfaces\Numbers\DecimalInterface;
13
use Samsara\Fermat\Types\Base\Interfaces\Evaluateables\FunctionInterface;
14
use Samsara\Fermat\Types\Base\Interfaces\Numbers\NumberInterface;
15
use Samsara\Fermat\Values\ImmutableDecimal;
16
17
class Normal extends Distribution
18
{
19
20
    /**
21
     * @var ImmutableDecimal
22
     */
23
    private $mean;
24
25
    /**
26
     * @var ImmutableDecimal
27
     */
28
    private $sd;
29
30
    /**
31
     * Normal constructor.
32
     *
33
     * @param int|float|DecimalInterface $mean
34
     * @param int|float|DecimalInterface $sd
35
     * @throws IntegrityConstraint
36
     */
37 4
    public function __construct($mean, $sd)
38
    {
39 4
        $mean = Numbers::makeOrDont(Numbers::IMMUTABLE, $mean);
40 4
        $sd = Numbers::makeOrDont(Numbers::IMMUTABLE, $sd);
41
42 4
        $this->mean = $mean;
0 ignored issues
show
Documentation Bug introduced by
It seems like $mean can also be of type Samsara\Fermat\Values\MutableDecimal. However, the property $mean is declared as type Samsara\Fermat\Values\ImmutableDecimal. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
43 4
        $this->sd = $sd;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sd can also be of type Samsara\Fermat\Values\MutableDecimal. However, the property $sd is declared as type Samsara\Fermat\Values\ImmutableDecimal. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
44 4
    }
45
46
    public function getSD()
47
    {
48
        return $this->sd;
49
    }
50
51
    public function getMean()
52
    {
53
        return $this->mean;
54
    }
55
56
    public function evaluateAt($x): ImmutableDecimal
57
    {
58
59
        $one = Numbers::makeOne();
60
        $twoPi = Numbers::make2Pi();
61
        $e = Numbers::makeE();
62
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
63
64
        $left = $one->divide($twoPi->multiply($this->getSD()->pow(2))->sqrt());
0 ignored issues
show
Bug introduced by
The call to Samsara\Fermat\Types\Bas...NumberInterface::sqrt() has too few arguments starting with precision. ( Ignorable by Annotation )

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

64
        $left = $one->divide($twoPi->multiply($this->getSD()->pow(2))->/** @scrutinizer ignore-call */ sqrt());

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
65
        $right = $e->pow($x->subtract($this->getMean())->pow(2)->divide($this->getSD()->pow(2)->multiply(2))->multiply(-1));
66
67
        /** @var ImmutableDecimal $value */
68
        $value = $left->multiply($right);
69
70
        return $value;
71
72
    }
73
74
    /**
75
     * @param int|float|DecimalInterface $p
76
     * @param int|float|DecimalInterface $x
77
     * @param int|float|DecimalInterface $mean
78
     *
79
     * @return Normal
80
     * @throws IntegrityConstraint
81
     */
82
    public static function makeFromMean($p, $x, $mean): Normal
83
    {
84
        $one = Numbers::makeOne();
85
        $p = Numbers::makeOrDont(Numbers::IMMUTABLE, $p);
86
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
87
        $mean = Numbers::makeOrDont(Numbers::IMMUTABLE, $mean);
88
89
        $z = StatsProvider::inverseNormalCDF($one->subtract($p));
90
91
        $sd = $x->subtract($mean)->divide($z);
92
93
        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

93
        return new Normal($mean, /** @scrutinizer ignore-type */ $sd);
Loading history...
94
    }
95
96 3
    /**
97
     * @param int|float|DecimalInterface $p
98 3
     * @param int|float|DecimalInterface $x
99
     * @param int|float|DecimalInterface $sd
100 3
     *
101 3
     * @return Normal
102 3
     * @throws IntegrityConstraint
103
     */
104
    public static function makeFromSd($p, $x, $sd): Normal
105 3
    {
106 3
        $one = Numbers::makeOne();
107
        $p = Numbers::makeOrDont(Numbers::IMMUTABLE, $p);
108
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
109 3
        $sd = Numbers::makeOrDont(Numbers::IMMUTABLE, $sd);
110
111
        $z = StatsProvider::inverseNormalCDF($one->subtract($p));
112
113
        $mean = $x->subtract($z->multiply($sd));
114
115
        return new Normal($mean, $sd);
0 ignored issues
show
Bug introduced by
It seems like $mean can also be of type Samsara\Fermat\Types\Bas...mbers\FractionInterface and Samsara\Fermat\Values\ImmutableComplexNumber; however, parameter $mean 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

115
        return new Normal(/** @scrutinizer ignore-type */ $mean, $sd);
Loading history...
116
    }
117
118
    /**
119 1
     * @param int|float|DecimalInterface $x
120
     *
121
     * @return ImmutableDecimal
122 1
     * @throws IntegrityConstraint
123
     */
124 1
    public function cdf($x): ImmutableDecimal
125
    {
126
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
127
128
        $oneHalf = Numbers::make(Numbers::IMMUTABLE, '0.5');
129
        $one = Numbers::makeOne();
130
        $sqrtTwo = Numbers::make(Numbers::IMMUTABLE, 2)->sqrt();
131
132
        /** @var ImmutableDecimal $cdf */
133
        $cdf = $oneHalf->multiply($one->add(StatsProvider::gaussErrorFunction(
0 ignored issues
show
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

133
        /** @scrutinizer ignore-call */ 
134
        $cdf = $oneHalf->multiply($one->add(StatsProvider::gaussErrorFunction(
Loading history...
134
            $x->subtract($this->mean)->divide($this->sd->multiply($sqrtTwo))
135 1
        )));
136
137 1
        return $cdf;
138
    }
139
140
    /**
141
     * @param int|float|DecimalInterface $x1
142
     * @param null|int|float|DecimalInterface $x2
143
     *
144
     * @return ImmutableDecimal
145
     * @throws IntegrityConstraint
146
     */
147
    public function pdf($x1, $x2 = null): ImmutableDecimal
148
    {
149
150
        $x1 = Numbers::makeOrDont(Numbers::IMMUTABLE, $x1);
151
152
        if (is_null($x2)) {
153
            $separation = $x1->subtract($this->mean)->multiply(2)->abs();
154
155
            if ($this->mean->isLessThan($x1)) {
156
                $x2 = $x1->subtract($separation);
157 2
            } else {
158
                $x2 = $x1->add($separation);
159 2
            }
160
        }
161 2
162
        /** @var ImmutableDecimal $pdf */
163
        $pdf = $this->cdf($x1)->subtract($this->cdf($x2))->abs();
164
165
        return $pdf;
166
    }
167
168
    /**
169
     * @param FunctionInterface $function
170 1
     * @param $x
171
     * @return ImmutableDecimal
172
     * @throws IntegrityConstraint
173 1
     */
174
    public function cdfProduct(FunctionInterface $function, $x): ImmutableDecimal
175
    {
176 1
177
        $loop = 0;
178 1
179
        $cdf = Numbers::makeZero();
180
181
        while (true) {
182
            if (count($function->describeShape()) == 0) {
183
                break;
184
            }
185
186
            $cdf = $cdf->add($function->evaluateAt($x)->multiply(SequenceProvider::nthPowerNegativeOne($loop)));
187 1
188
            $function = $function->derivativeExpression();
189 1
        }
190
191
        /** @var ImmutableDecimal $cdf */
192 1
        $cdf = $cdf->multiply($this->cdf($x));
193
194 1
        return $cdf;
195
196
    }
197
198
    public function pdfProduct(FunctionInterface $function, $x1, $x2): ImmutableDecimal
199
    {
200
        /** @var ImmutableDecimal $pdf */
201
        $pdf = $this->cdfProduct($function, $x2)->subtract($this->cdfProduct($function, $x1));
202
203
        return $pdf;
204
    }
205
206
    /**
207
     * @param int|float|DecimalInterface $x
208
     *
209
     * @return ImmutableDecimal
210
     * @throws IntegrityConstraint
211
     */
212
    public function percentBelowX($x): ImmutableDecimal
213
    {
214
        return $this->cdf($x);
215
    }
216
217
    /**
218
     * @param int|float|DecimalInterface $x
219
     *
220
     * @return ImmutableDecimal
221
     * @throws IntegrityConstraint
222
     */
223
    public function percentAboveX($x): ImmutableDecimal
224
    {
225
        $one = Numbers::makeOne();
226
227
        /** @var ImmutableDecimal $perc */
228
        $perc = $one->subtract($this->cdf($x));
229
230
        return $perc;
231
    }
232
233
    /**
234
     * @param int|float|DecimalInterface $x
235
     *
236
     * @return ImmutableDecimal
237
     * @throws IntegrityConstraint
238
     */
239
    public function zScoreOfX($x): ImmutableDecimal
240
    {
241
        /** @var ImmutableDecimal $x */
242
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
243
244
        /** @var ImmutableDecimal $z */
245
        $z = $x->subtract($this->mean)->divide($this->sd);
246
247
        return $z;
248
    }
249
250
    /**
251
     * @param int|float|DecimalInterface $z
252
     *
253
     * @return ImmutableDecimal
254
     * @throws IntegrityConstraint
255
     */
256
    public function xFromZScore($z): ImmutableDecimal
257
    {
258
        $z = Numbers::makeOrDont(Numbers::IMMUTABLE, $z);
259
260
        /** @var ImmutableDecimal $x */
261
        $x = $z->multiply($this->sd)->add($this->mean);
262
263
        return $x;
264
    }
265
266
    /**
267
     * @return ImmutableDecimal
268
     * @throws IntegrityConstraint
269
     */
270
    public function random(): ImmutableDecimal
271
    {
272
        $int1 = PolyfillProvider::randomInt(0, PHP_INT_MAX);
273
        $int2 = PolyfillProvider::randomInt(0, PHP_INT_MAX);
274
275
        $rand1 = Numbers::make(Numbers::IMMUTABLE, $int1, 20);
276
        $rand1 = $rand1->divide(PHP_INT_MAX);
0 ignored issues
show
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

276
        /** @scrutinizer ignore-call */ $rand1 = $rand1->divide(PHP_INT_MAX);
Loading history...
277
        $rand2 = Numbers::make(Numbers::IMMUTABLE, $int2, 20);
278
        $rand2 = $rand2->divide(PHP_INT_MAX);
279
280
        $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\ImmutableComplexNumber. ( Ignorable by Annotation )

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

280
        $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\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

280
        $randomNumber = $rand1->/** @scrutinizer ignore-call */ ln()->multiply(-2)->sqrt()->multiply($rand2->multiply(Numbers::TAU)->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

280
        $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 call to Samsara\Fermat\Types\Bas...NumberInterface::sqrt() has too few arguments starting with precision. ( Ignorable by Annotation )

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

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

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
281
        /** @var ImmutableDecimal $randomNumber */
282
        $randomNumber = $randomNumber->multiply($this->sd)->add($this->mean);
283
284
        return $randomNumber;
285
    }
286
287
    /**
288
     * @param int|float|NumberInterface $min
289
     * @param int|float|NumberInterface $max
290
     * @param int $maxIterations
291
     *
292
     * @return ImmutableDecimal
293
     * @throws OptionalExit
294
     * @throws IntegrityConstraint
295
     */
296
    public function rangeRandom($min = 0, $max = PHP_INT_MAX, int $maxIterations = 20): ImmutableDecimal
297
    {
298
        $i = 0;
299
300
        do {
301
            $randomNumber = $this->random();
302
            $i++;
303
        } while (($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) && $i < $maxIterations);
304
305
        if ($randomNumber->isGreaterThan($max) || $randomNumber->isLessThan($min)) {
306
            throw new OptionalExit(
307
                'All random numbers generated were outside of the requested range',
308
                '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

308
                '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

308
                'A suitable random number, restricted by the $max ('./** @scrutinizer ignore-type */ $max.') and $min ('.$min.'), could not be found within '.$maxIterations.' iterations'
Loading history...
309
            );
310
        }
311
312
        return $randomNumber;
313
    }
314
315
}