StatsProvider::complementNormalCDF()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
c 1
b 0
f 0
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace Samsara\Fermat\Stats\Provider;
4
5
use Ds\Vector;
6
use ReflectionException;
7
use Samsara\Exceptions\SystemError\PlatformError\MissingPackage;
8
use Samsara\Exceptions\UsageError\IntegrityConstraint;
9
use Samsara\Exceptions\SystemError\LogicalError\IncompatibleObjectState;
10
use Samsara\Exceptions\UsageError\OptionalExit;
11
use Samsara\Fermat\Core\Numbers;
12
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...
13
use Samsara\Fermat\Core\Types\Base\Interfaces\Numbers\DecimalInterface;
14
use Samsara\Fermat\Core\Values\ImmutableDecimal;
15
use Samsara\Fermat\Core\Values\ImmutableFraction;
16
use Samsara\Fermat\Core\Provider\SeriesProvider;
17
use Samsara\Fermat\Core\Provider\SequenceProvider;
18
19
class StatsProvider
20
{
21
22
    /**
23
     * @var Vector|null
24
     */
25
    protected static ?Vector $inverseErrorCoefs = null;
26
27
    /**
28
     * @param $x
29
     *
30
     * @return ImmutableDecimal
31
     * @throws IntegrityConstraint
32
     * @throws MissingPackage
33
     * @throws OptionalExit
34
     * @throws ReflectionException
35
     */
36 4
    public static function normalCDF($x): ImmutableDecimal
37
    {
38 4
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
39
40 4
        $scale = $x->getScale();
41 4
        $internalScale = $scale+2;
42
43 4
        $pi = Numbers::makePi($internalScale);
44 4
        $e = Numbers::makeE($internalScale);
45 4
        $one = Numbers::makeOne($internalScale);
46
47 4
        $eExponent = Numbers::make(Numbers::IMMUTABLE, $x->getValue());
48 4
        $eExponent = $eExponent->pow(2)->divide(2)->multiply(-1);
49
50 4
        $answer = Numbers::make(Numbers::IMMUTABLE, 0.5);
51 4
        $answer = $answer->add(
52 4
            $one->divide($pi->multiply(2)->sqrt())
53 4
                ->multiply($e->pow($eExponent))
54 4
                ->multiply(SeriesProvider::maclaurinSeries(
55
                    $x,
56 4
                    function ($n) {
0 ignored issues
show
Unused Code introduced by
The parameter $n is not used and could be removed. ( Ignorable by Annotation )

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

56
                    function (/** @scrutinizer ignore-unused */ $n) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
57 4
                        return Numbers::makeOne();
58
                    },
59 4
                    function ($n) {
60 4
                        return SequenceProvider::nthOddNumber($n);
61
                    },
62 4
                    function ($n) {
63 4
                        return SequenceProvider::nthOddNumber($n)->doubleFactorial();
64
                    },
65
                    0,
66
                    $internalScale
67
                ))
68
        );
69
70 4
        return $answer->truncateToScale($scale);
71
72
    }
73
74
    /**
75
     * @param $x
76
     *
77
     * @return ImmutableDecimal
78
     * @throws IntegrityConstraint
79
     * @throws MissingPackage
80
     * @throws OptionalExit
81
     * @throws ReflectionException
82
     */
83 2
    public static function complementNormalCDF($x): ImmutableDecimal
84
    {
85 2
        $p = self::normalCDF($x);
86 2
        $one = Numbers::makeOne();
87
88 2
        return $one->subtract($p);
89
    }
90
91
    /**
92
     * @param $x
93
     *
94
     * @return ImmutableDecimal
95
     * @throws IntegrityConstraint
96
     * @throws OptionalExit
97
     * @throws ReflectionException
98
     */
99 4
    public static function gaussErrorFunction($x): ImmutableDecimal
100
    {
101
102 4
        $x = Numbers::makeOrDont(Numbers::IMMUTABLE, $x);
103
104 4
        $scale = $x->getScale();
105 4
        $internalScale = $scale + 2;
106
107 4
        $answer = Numbers::makeOne($internalScale);
108 4
        $pi = Numbers::makePi($internalScale);
109
110 4
        $answer = $answer->multiply(2)->divide($pi->sqrt());
111
112 4
        $answer = $answer->multiply(
113 4
            SeriesProvider::maclaurinSeries(
114
                $x,
115 4
                function ($n) {
116 4
                    $negOne = Numbers::make(Numbers::IMMUTABLE, -1);
117
118 4
                    return $negOne->pow($n);
119
                },
120 4
                function ($n) {
121 4
                    return SequenceProvider::nthOddNumber($n);
122
                },
123 4
                function ($n) {
124 4
                    $n = Numbers::makeOrDont(Numbers::IMMUTABLE, $n);
125
126 4
                    return $n->factorial()->multiply(SequenceProvider::nthOddNumber($n->asInt()));
127
                },
128
                0,
129
                $internalScale
130
            )
131
        );
132
133 4
        return $answer->truncateToScale($scale);
134
135
    }
136
137
    /**
138
     * @param     $p
139
     * @param int $scale
140
     *
141
     * @return ImmutableDecimal
142
     * @throws IntegrityConstraint
143
     * @throws OptionalExit
144
     * @throws ReflectionException
145
     * @throws MissingPackage
146
     */
147 2
    public static function inverseNormalCDF($p, int $scale = 10): ImmutableDecimal
148
    {
149 2
        $p = Numbers::makeOrDont(Numbers::IMMUTABLE, $p);
150
151 2
        $scale = $scale ?? $p->getScale();
152 2
        $internalScale = $scale + $p->numberOfDecimalDigits();
153
154 2
        $two = Numbers::make(Numbers::IMMUTABLE, 2, $internalScale);
155 2
        $invErfArg = $two->multiply($p)->subtract(1);
156
157 2
        return StatsProvider::inverseGaussErrorFunction($invErfArg, $internalScale)->multiply($two->sqrt($internalScale))->roundToScale($scale);
158
    }
159
160
    /**
161
     * @param $n
162
     * @param $k
163
     *
164
     * @return DecimalInterface|NumberInterface|ImmutableDecimal
165
     * @throws IntegrityConstraint
166
     * @throws IncompatibleObjectState
167
     */
168 8
    public static function binomialCoefficient($n, $k): ImmutableDecimal
169
    {
170
171 8
        $n = Numbers::makeOrDont(Numbers::IMMUTABLE, $n);
172 8
        $k = Numbers::makeOrDont(Numbers::IMMUTABLE, $k);
173
174 8
        if ($k->isLessThan(0) || $n->isLessThan($k)) {
175 4
            throw new IntegrityConstraint(
176
                '$k must be larger or equal to 0 and less than or equal to $n',
177
                'Provide valid $n and $k values such that 0 <= $k <= $n',
178
                'For $n choose $k, the values of $n and $k must satisfy the inequality 0 <= $k <= $n'
179
            );
180
        }
181
182 4
        if (!$n->isInt() || !$k->isInt()) {
183 2
            throw new IntegrityConstraint(
184
                '$k and $n must be whole numbers',
185
                'Provide whole numbers for $n and $k',
186
                'For $n choose $k, the values $n and $k must be whole numbers'
187
            );
188
        }
189
190 2
        return $n->factorial()->divide($k->factorial()->multiply($n->subtract($k)->factorial()));
191
192
    }
193
194 6
    public static function inverseErrorCoefficients(int $termIndex): ImmutableFraction
195
    {
196
197 6
        if (is_null(static::$inverseErrorCoefs)) {
198 2
            static::$inverseErrorCoefs = new Vector();
199 2
            static::$inverseErrorCoefs->push(new ImmutableFraction(Numbers::makeOne(), Numbers::makeOne()));
0 ignored issues
show
Bug introduced by
The method push() does not exist on null. ( Ignorable by Annotation )

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

199
            static::$inverseErrorCoefs->/** @scrutinizer ignore-call */ 
200
                                        push(new ImmutableFraction(Numbers::makeOne(), Numbers::makeOne()));

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...
200 2
            static::$inverseErrorCoefs->push(new ImmutableFraction(Numbers::makeOne(), Numbers::makeOne()));
201
        }
202
203 6
        $terms =& static::$inverseErrorCoefs;
204
205 6
        if ($terms->offsetExists($termIndex)) {
0 ignored issues
show
Bug introduced by
The method offsetExists() does not exist on null. ( Ignorable by Annotation )

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

205
        if ($terms->/** @scrutinizer ignore-call */ offsetExists($termIndex)) {

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 offsetExists() does not exist on Ds\Vector. ( Ignorable by Annotation )

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

205
        if ($terms->/** @scrutinizer ignore-call */ offsetExists($termIndex)) {

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...
206 6
            return $terms->get($termIndex);
207
        }
208
209 2
        $nextTerm = $terms->count();
210
211 2
        for ($k = $nextTerm;$k <= $termIndex;$k++) {
212 2
            $termValue = new ImmutableFraction(new ImmutableDecimal('0'), new ImmutableDecimal('1'));
213 2
            for ($m = 0;$m <= ($k - 1);$m++) {
214 2
                $part1 = $terms->get($m);
215 2
                $part2 = $terms->get($k - 1 - $m);
216 2
                $part3 = $part1->multiply($part2);
217 2
                $part4 = ($m + 1)*($m*2 + 1);
218 2
                $part5 = $part3->divide($part4);
219 2
                $termValue = $termValue->add($part5);
220
            }
221
222 2
            $termValue = $termValue->simplify();
223
224 2
            $terms->push($termValue);
225
        }
226
227 2
        return $terms->get($termIndex);
228
229
    }
230
231
    /**
232
     * @param $z
233
     * @param int $scale
234
     *
235
     * @return ImmutableDecimal
236
     * @throws IntegrityConstraint
237
     * @throws OptionalExit
238
     * @throws ReflectionException
239
     */
240 4
    public static function inverseGaussErrorFunction($z, int $scale = 10): ImmutableDecimal
241
    {
242
243 4
        $z = Numbers::makeOrDont(Numbers::IMMUTABLE, $z);
244
245 4
        $scale = $scale ?? $z->getScale();
246 4
        $internalScale = $scale + $z->numberOfDecimalDigits();
247
248 4
        $pi = Numbers::makePi($internalScale);
249
250 4
        $answer = SeriesProvider::maclaurinSeries(
251
            $z,
252 4
            static function ($n) use ($pi) {
253 4
                if ($n > 0) {
254 4
                    return $pi->pow($n)->multiply(StatsProvider::inverseErrorCoefficients($n));
255
                }
256
257 4
                return Numbers::makeOne();
258
            },
259 4
            static function ($n) {
260 4
                return SequenceProvider::nthOddNumber($n);
261
            },
262 4
            static function ($n) {
263 4
                if ($n > 0) {
264 4
                    $extra = Numbers::make(Numbers::IMMUTABLE, 2)->pow(SequenceProvider::nthEvenNumber($n));
265
                } else {
266 4
                    $extra = Numbers::makeOne();
267
                }
268
269 4
                return SequenceProvider::nthOddNumber($n)->multiply($extra);
270
            },
271
            0,
272
            $internalScale
273
        );
274
275 4
        $answer = $answer->multiply($pi->sqrt($internalScale)->divide(2, $internalScale));
276
277 4
        return $answer->roundToScale($scale);
278
279
    }
280
281
}