Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.87% covered (warning)
85.87%
79 / 92
76.92% covered (warning)
76.92%
10 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
Functions
85.87% covered (warning)
85.87%
79 / 92
76.92% covered (warning)
76.92%
10 / 13
45.74
0.00% covered (danger)
0.00%
0 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 fact
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 binomialCoefficient
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
 ackermann
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 invMod
95.24% covered (success)
95.24%
20 / 21
0.00% covered (danger)
0.00%
0 / 1
6
 mod
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isOdd
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isEven
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRelativeDegree
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getErf
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getErfc
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 erfccheb
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
 getInvErfc
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 generalizedHypergeometricFunction
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Math\Functions
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Math\Functions;
16
17/**
18 * Well known functions and helpers class.
19 *
20 * @package phpOMS\Math\Functions
21 * @license OMS License 2.0
22 * @link    https://jingga.app
23 * @since   1.0.0
24 */
25final class Functions
26{
27    /**
28     * Epsilon for float comparison.
29     *
30     * @var float
31     * @since 1.0.0
32     */
33    public const EPSILON = 4.88e-04;
34
35    /**
36     * Constructor.
37     *
38     * @since 1.0.0
39     * @codeCoverageIgnore
40     */
41    private function __construct()
42    {
43    }
44
45    /**
46     * Calculate gammar function value.
47     *
48     * Example: (7, 2)
49     *
50     * @param int $n     Factorial upper bound
51     * @param int $start Factorial starting value
52     *
53     * @return int
54     *
55     * @since 1.0.0
56     */
57    public static function fact(int $n, int $start = 1) : int
58    {
59        $fact = 1;
60
61        for ($i = $start; $i < $n + 1; ++$i) {
62            $fact *= $i;
63        }
64
65        return $fact;
66    }
67
68    /**
69     * Calculate binomial coefficient
70     *
71     * Algorithm optimized for large factorials without the use of big int or string manipulation.
72     *
73     * Example: (7, 2)
74     *
75     * @param int $n n
76     * @param int $k k
77     *
78     * @return int
79     *
80     * @since 1.0.0
81     */
82    public static function binomialCoefficient(int $n, int $k) : int
83    {
84        $max = \max([$k, $n - $k]);
85        $min = \min([$k, $n - $k]);
86
87        $fact  = 1;
88        $range = \array_reverse(\range(1, $min));
89
90        for ($i = $max + 1; $i < $n + 1; ++$i) {
91            $div = 1;
92            foreach ($range as $key => $d) {
93                if ($i % $d === 0) {
94                    $div = $d;
95
96                    unset($range[$key]);
97                    break;
98                }
99            }
100
101            $fact *= $i / $div;
102        }
103
104        $fact2 = 1;
105
106        foreach ($range as $d) {
107            $fact2 *= $d;
108        }
109
110        return (int) ($fact / $fact2);
111    }
112
113    /**
114     * Calculate ackermann function.
115     *
116     * @param int $m m
117     * @param int $n n
118     *
119     * @return int
120     *
121     * @since 1.0.0
122     */
123    public static function ackermann(int $m, int $n) : int
124    {
125        if ($m === 0) {
126            return $n + 1;
127        } elseif ($n === 0) {
128            return self::ackermann($m - 1, 1);
129        }
130
131        return self::ackermann($m - 1, self::ackermann($m, $n - 1));
132    }
133
134    /**
135     * Calculate inverse modular.
136     *
137     * @param int $a a
138     * @param int $n Modulo
139     *
140     * @return int
141     *
142     * @since 1.0.0
143     */
144    public static function invMod(int $a, int $n) : int
145    {
146        if ($n < 0) {
147            $n = -$n;
148        }
149
150        if ($a < 0) {
151            $a = $n - (-$a % $n);
152        }
153
154        $t  = 0;
155        $nt = 1;
156        $r  = $n;
157        $nr = $a % $n;
158
159        while ($nr != 0) {
160            $quot = (int) ($r / $nr);
161            $tmp  = $nt;
162            $nt   = $t - $quot * $nt;
163            $t    = $tmp;
164            $tmp  = $nr;
165            $nr   = $r - $quot * $nr;
166            $r    = $tmp;
167        }
168
169        if ($r > 1) {
170            return -1;
171        }
172
173        if ($t < 0) {
174            $t += $n;
175        }
176
177        return $t;
178    }
179
180    /**
181     * Modular implementation for negative values.
182     *
183     * @param int $a a
184     * @param int $b b
185     *
186     * @return int
187     *
188     * @since 1.0.0
189     */
190    public static function mod(int $a, int $b) : int
191    {
192        if ($a < 0) {
193            return ($a + $b) % $b;
194        }
195
196        return $a % $b;
197    }
198
199    /**
200     * Check if value is odd.
201     *
202     * @param int $a Value to test
203     *
204     * @return bool
205     *
206     * @since 1.0.0
207     */
208    public static function isOdd(int $a) : bool
209    {
210        return (bool) ($a & 1);
211    }
212
213    /**
214     * Check if value is even.
215     *
216     * @param int $a Value to test
217     *
218     * @return bool
219     *
220     * @since 1.0.0
221     */
222    public static function isEven(int $a) : bool
223    {
224        return !((bool) ($a & 1));
225    }
226
227    /**
228     * Gets the relative position on a circular construct.
229     *
230     * @example The relative fiscal month (August) in a company where the fiscal year starts in July.
231     * @example 2 = getRelativeDegree(8, 12, 7);
232     *
233     * @param int $value  Value to get degree
234     * @param int $length Circle size
235     * @param int $start  Start value
236     *
237     * @return int Lowest value is 0 and highest value is length - 1
238     *
239     * @since 1.0.0
240     */
241    public static function getRelativeDegree(int $value, int $length, int $start = 0) : int
242    {
243        return \abs(self::mod($value - $start, $length));
244    }
245
246    /**
247     * Error function coefficients for approximation
248     *
249     * @var float[]
250     * @since 1.0.0
251     */
252    private const ERF_COF = [
253        -1.3026537197817094, 6.4196979235649026e-1,
254        1.9476473204185836e-2,-9.561514786808631e-3,-9.46595344482036e-4,
255        3.66839497852761e-4,4.2523324806907e-5,-2.0278578112534e-5,
256        -1.624290004647e-6,1.303655835580e-6,1.5626441722e-8,-8.5238095915e-8,
257        6.529054439e-9,5.059343495e-9,-9.91364156e-10,-2.27365122e-10,
258        9.6467911e-11, 2.394038e-12,-6.886027e-12,8.94487e-13, 3.13092e-13,
259        -1.12708e-13,3.81e-16,7.106e-15,-1.523e-15,-9.4e-17,1.21e-16,-2.8e-17,
260    ];
261
262    /**
263     * Error function
264     *
265     * @param float $x X-Value
266     *
267     * @return float
268     *
269     * @since 1.0.0
270     */
271    public static function getErf(float $x) : float
272    {
273        return $x > 0.0
274            ? 1.0 - self::erfccheb($x)
275            : self::erfccheb(-$x) - 1.0;
276    }
277
278    /**
279     * Complementary error function
280     *
281     * @param float $x X-Value
282     *
283     * @return float
284     *
285     * @since 1.0.0
286     */
287    public static function getErfc(float $x) : float
288    {
289        return $x > 0.0
290            ? self::erfccheb($x)
291            : 2.0 - self::erfccheb(-$x);
292    }
293
294    /**
295     * Error function helper function
296     *
297     * @param float $z Z-Value
298     *
299     * @return float
300     *
301     * @throws \InvalidArgumentException
302     *
303     * @since 1.0.0
304     */
305    private static function erfccheb(float $z) : float
306    {
307        $d  = 0.;
308        $dd = 0.;
309
310        $ncof = \count(self::ERF_COF);
311
312        if ($z < 0.) {
313            throw new \InvalidArgumentException("erfccheb requires nonnegative argument");
314        }
315
316        $t  = 2. / (2. + $z);
317        $ty = 4. * $t - 2.;
318
319        for ($j = $ncof - 1; $j > 0; --$j) {
320            $tmp = $d;
321            $d   = $ty * $d - $dd + self::ERF_COF[$j];
322            $dd  = $tmp;
323        }
324
325        return $t * \exp(-$z * $z + 0.5 * (self::ERF_COF[0] + $ty * $d) - $dd);
326    }
327
328    /**
329     * Inverse complementary error function
330     *
331     * @param float $p P-Value
332     *
333     * @return float
334     *
335     * @since 1.0.0
336     */
337    public static function getInvErfc(float $p) : float
338    {
339        if ($p >= 2.0) {
340            return -100.;
341        } elseif ($p <= 0.0) {
342            return 100.;
343        }
344
345        $pp = ($p < 1.0) ? $p : 2. - $p;
346        $t  = \sqrt(-2. * \log($pp / 2.));
347        $x  = -0.70711 * ((2.30753 + $t * 0.27061) / (1. + $t * (0.99229 + $t * 0.04481)) - $t);
348
349        for ($j = 0; $j < 2; ++$j) {
350            $err = self::getErfc($x) - $pp;
351            $x  += $err / (1.12837916709551257 * \exp(-($x * $x)) - $x * $err);
352        }
353
354        return ($p < 1.0? $x : -$x);
355    }
356
357    /**
358     * Generalized hypergeometric function.
359     *
360     * pFq(a1, ..., ap; b1, ..., bq; z)
361     *
362     * @param array<int, float|int> $a Array of values
363     * @param array<int, float|int> $b Array of values
364     * @param float                 $z Z
365     *
366     * @return float
367     *
368     * @since 1.0.0
369     */
370    public static function generalizedHypergeometricFunction(array $a, array $b, float $z) : float
371    {
372        $sum   = 0.0;
373        $aProd = \array_fill(0, 20, []);
374        $bProd = \array_fill(0, 20, []);
375
376        for ($n = 0; $n < 20; ++$n) {
377            foreach ($a as $key => $value) {
378                $aProd[$n][$key] = $n === 0 ? 1 : $aProd[$n - 1][$key] * ($value + $n - 1);
379            }
380
381            foreach ($b as $key => $value) {
382                $bProd[$n][$key] = $n === 0 ? 1 : $bProd[$n - 1][$key] * ($value + $n - 1);
383            }
384
385            $temp = \array_product($aProd[$n]) / \array_product($bProd[$n]);
386            $sum += $temp * $z ** $n / self::fact($n);
387        }
388
389        return $sum;
390    }
391}