• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

markrogoyski / math-php / 3811069246

pending completion
3811069246

push

github

Mark Rogoyski
Update CHANGELOG for v.2.7.0.

7500 of 7506 relevant lines covered (99.92%)

185.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.68
/src/Functions/Special.php
1
<?php
2

3
namespace MathPHP\Functions;
4

5
use MathPHP\Probability\Combinatorics;
6
use MathPHP\Functions\Map\Single;
7
use MathPHP\Exception;
8

9
class Special
10
{
11
    /**
12
     * Sign function (signum function) - sgn
13
     * Extracts the sign of a real number.
14
     * https://en.wikipedia.org/wiki/Sign_function
15
     *
16
     *          { -1 if x < 0
17
     * sgn(x) = {  0 if x = 0
18
     *          {  1 if x > 0
19
     *
20
     * @param float $x
21
     *
22
     * @return int
23
     */
24
    public static function signum(float $x): int
25
    {
26
        return $x <=> 0;
289✔
27
    }
28

29
    /**
30
     * Sign function (signum function) - sgn
31
     * Convenience wrapper for signum function.
32
     *
33
     * @param float $x
34
     *
35
     * @return int
36
     */
37
    public static function sgn(float $x): int
38
    {
39
        return self::signum($x);
289✔
40
    }
41

42
    /**
43
     * Gamma function convenience method
44
     *
45
     * @param float $n
46
     *
47
     * @return float
48
     *
49
     * @throws Exception\OutOfBoundsException
50
     */
51
    public static function Γ(float $n): float
52
    {
53
        return self::gamma($n);
178✔
54
    }
55

56
    /**
57
     * Gamma function - Lanczos' approximation
58
     * https://en.wikipedia.org/wiki/Gamma_function
59
     * https://en.wikipedia.org/wiki/Lanczos_approximation
60
     *
61
     * For postive integers:
62
     *  Γ(n) = (n - 1)!
63
     *
64
     * If z is < 0.5, use reflection formula:
65
     *
66
     *                   π
67
     *  Γ(1 - z)Γ(z) = ------
68
     *                 sin πz
69
     *
70
     *  therefore:
71
     *
72
     *                π
73
     *  Γ(z) = -----------------
74
     *         sin πz * Γ(1 - z)
75
     *
76
     * otherwise:
77
     *              __  /        1 \ z+½
78
     *  Γ(z + 1) = √2π | z + g + -  |    e^-(z+g+½) A(z)
79
     *                  \        2 /
80
     *
81
     *  use pre-computed p coefficients: g = 7, n = 9
82
     *
83
     * @param float $z
84
     *
85
     * @return float
86
     *
87
     * @throws Exception\OutOfBoundsException
88
     */
89
    public static function gammaLanczos(float $z): float
90
    {
91
        // Basic integer/factorial cases
92
        if ($z == 0) {
42✔
93
            return \INF;
2✔
94
        }
95
        // Negative integer, or negative int as a float
96
        if ((\abs($z - \round($z)) < 0.00001) && $z < 0) {
40✔
97
            return -\INF;
3✔
98
        }
99
        // Positive integer, or positive int as a float (Ex: from beta(0.1, 0.9) since it will call Γ(x + y))
100
        if ((\abs($z - \round($z)) < 0.00001) && $z > 0) {
37✔
101
            return Combinatorics::factorial((int) \round($z) - 1);
12✔
102
        }
103

104
        // p coefficients: g = 7, n = 9
105
        static $p = [
25✔
106
            0.99999999999980993227684700473478,
107
            676.520368121885098567009190444019,
108
            -1259.13921672240287047156078755283,
109
            771.3234287776530788486528258894,
110
            -176.61502916214059906584551354,
111
            12.507343278686904814458936853,
112
            -0.13857109526572011689554707,
113
            9.984369578019570859563e-6,
114
            1.50563273514931155834e-7,
115
        ];
116
        static $g = 7;
25✔
117
        static $π = \M_PI;
25✔
118

119
        /**
120
         * Use reflection formula when z < 0.5
121
         *                π
122
         *  Γ(z) = -----------------
123
         *         sin πz * Γ(1 - z)
124
         */
125
        if ($z < 0.5) {
25✔
126
            $Γ⟮1 − z⟯ = self::gammaLanczos(1 - $z);
8✔
127
            return $π / ( \sin($π * $z) * $Γ⟮1 − z⟯);
8✔
128
        }
129

130
        // Standard Lanczos formula when z ≥ 0.5
131

132
        // Compute A(z)
133
        $z--;
25✔
134
        $A⟮z⟯ = $p[0];
25✔
135
        for ($i = 1; $i < \count($p); $i++) {
25✔
136
            $A⟮z⟯ += $p[$i] / ($z + $i);
25✔
137
        }
138

139
        // Compute parts of equation
140
        $√2π = \sqrt(2 * $π);
25✔
141
        $⟮z + g +½⟯ᶻ⁺½ = \pow($z + $g + 0.5, $z + 0.5);
25✔
142
        $ℯ^−⟮z + g +½⟯ = \exp(-($z + $g + 0.5));
25✔
143

144
        /**
145
         * Put it all together:
146
         *   __  /        1 \ z+½
147
         *  √2π | z + g + -  |    e^-(z+g+½) A(z)
148
         *       \        2 /
149
         */
150
        return $√2π * $⟮z + g +½⟯ᶻ⁺½ * $ℯ^−⟮z + g +½⟯ * $A⟮z⟯;
25✔
151
    }
152

153
    /**
154
     * Gamma function - Stirling approximation
155
     * https://en.wikipedia.org/wiki/Gamma_function
156
     * https://en.wikipedia.org/wiki/Stirling%27s_approximation
157
     * https://www.wolframalpha.com/input/?i=Gamma(n)&lk=3
158
     *
159
     * For postive integers:
160
     *  Γ(n) = (n - 1)!
161
     *
162
     * For positive real numbers -- approximation:
163
     *                   ___
164
     *         __       / 1  /         1      \ n
165
     *  Γ(n)≈ √2π ℯ⁻ⁿ  /  - | n + ----------- |
166
     *                √   n  \    12n - 1/10n /
167
     *
168
     * @param float $n
169
     *
170
     * @return float
171
     *
172
     * @throws Exception\OutOfBoundsException
173
     */
174
    public static function gammaStirling(float $n): float
175
    {
176
        // Basic integer/factorial cases
177
        if ($n == 0) {
17✔
178
            return \INF;
1✔
179
        }
180
        // Negative integer, or negative int as a float
181
        if ((\abs($n - \round($n)) < 0.00001) && $n < 0) {
16✔
182
            return -\INF;
3✔
183
        }
184
        // Positive integer, or postive int as a float
185
        if ((\abs($n - \round($n)) < 0.00001) && $n > 0) {
13✔
186
            return Combinatorics::factorial((int) \round($n) - 1);
7✔
187
        }
188

189
        // Compute parts of equation
190
        $√2π                    = \sqrt(2 * \M_PI);
6✔
191
        $ℯ⁻ⁿ                    = \exp(-$n);
6✔
192
        $√1/n                  = \sqrt(1 / $n);
6✔
193
        $⟮n + 1/⟮12n − 1/10n⟯⟯ⁿ = \pow($n + 1 / (12 * $n - 1 / (10 * $n)), $n);
6✔
194

195
        /**
196
         * Put it all together:
197
         *                   ___
198
         *         __       / 1  /         1      \ n
199
         *  Γ(n)≈ √2π ℯ⁻ⁿ  /  - | n + ----------- |
200
         *                √   n  \    12n - 1/10n /
201
         */
202
        return $√2π * $ℯ⁻ⁿ * $√1/n * $⟮n + 1/⟮12n − 1/10n⟯⟯ⁿ;
6✔
203
    }
204

205
    /**
206
     * The log of the error term in the Stirling-De Moivre factorial series
207
     *
208
     * log(n!) = .5*log(2πn) + n*log(n) - n + δ(n)
209
     * Where δ(n) is the log of the error.
210
     *
211
     * For n ≤ 15, integers or half-integers, uses stored values.
212
     *
213
     * For n = 0: infinity
214
     * For n < 0: NAN
215
     *
216
     * The implementation is heavily inspired by the R language's C implementation of stirlerr.
217
     * It can be considered a reimplementation in PHP.
218
     * R Project for Statistical Computing: https://www.r-project.org/
219
     * R Source: https://svn.r-project.org/R/
220
     *
221
     * @param float $n
222
     *
223
     * @return float log of the error
224
     *
225
     * @throws Exception\NanException
226
     */
227
    public static function stirlingError(float $n): float
228
    {
229
        if ($n < 0) {
355✔
230
            throw new Exception\NanException("stirlingError NAN for n < 0: given $n");
3✔
231
        }
232

233
        static $S0 = 0.083333333333333333333;        // 1/12
352✔
234
        static $S1 = 0.00277777777777777777778;      // 1/360
352✔
235
        static $S2 = 0.00079365079365079365079365;   // 1/1260
352✔
236
        static $S3 = 0.000595238095238095238095238;  // 1/1680
352✔
237
        static $S4 = 0.0008417508417508417508417508; // 1/1188
352✔
238

239
        static $sferr_halves = [
352✔
240
            \INF,                          // 0
241
            0.1534264097200273452913848,   // 0.5
242
            0.0810614667953272582196702,   // 1.0
243
            0.0548141210519176538961390,   // 1.5
244
            0.0413406959554092940938221,   // 2.0
245
            0.03316287351993628748511048,  // 2.5
246
            0.02767792568499833914878929,  // 3.0
247
            0.02374616365629749597132920,  // 3.5
248
            0.02079067210376509311152277,  // 4.0
249
            0.01848845053267318523077934,  // 4.5
250
            0.01664469118982119216319487,  // 5.0
251
            0.01513497322191737887351255,  // 5.5
252
            0.01387612882307074799874573,  // 6.0
253
            0.01281046524292022692424986,  // 6.5
254
            0.01189670994589177009505572,  // 7.0
255
            0.01110455975820691732662991,  // 7.5
256
            0.010411265261972096497478567, // 8.0
257
            0.009799416126158803298389475, // 8.5
258
            0.009255462182712732917728637, // 9.0
259
            0.008768700134139385462952823, // 9.5
260
            0.008330563433362871256469318, // 10.0
261
            0.007934114564314020547248100, // 10.5
262
            0.007573675487951840794972024, // 11.0
263
            0.007244554301320383179543912, // 11.5
264
            0.006942840107209529865664152, // 12.0
265
            0.006665247032707682442354394, // 12.5
266
            0.006408994188004207068439631, // 13.0
267
            0.006171712263039457647532867, // 13.5
268
            0.005951370112758847735624416, // 14.0
269
            0.005746216513010115682023589, // 14.5
270
            0.005554733551962801371038690, // 15.0
271
        ];
272

273
        if ($n <= 15.0) {
352✔
274
            $nn = $n + $n;
216✔
275
            if ($nn == (int) $nn) {
216✔
276
                return $sferr_halves[$nn];
207✔
277
            }
278
            $M_LN_SQRT_2PI = \log(\sqrt(2 * \M_PI));
9✔
279
            return self::logGamma($n + 1) - ($n + 0.5) * \log($n) + $n - $M_LN_SQRT_2PI;
9✔
280
        }
281

282
        $n² = $n * $n;
150✔
283
        if ($n > 500) {
150✔
284
            return ($S0 - $S1 / $n²) / $n;
16✔
285
        }
286
        if ($n > 80) {
140✔
287
            return ($S0 - ($S1 - $S2 / $n²) / $n²) / $n;
63✔
288
        }
289
        if ($n > 35) {
79✔
290
            return ($S0 - ($S1 - ($S2 - $S3/$n²) / $n²) / $n²) / $n;
43✔
291
        }
292
        // 15 < n ≤ 35
293
        return ($S0 - ($S1 - ($S2 - ($S3 - $S4/$n²) / $n²) / $n²) / $n²) / $n;
43✔
294
    }
295

296
    /**
297
     * Gamma function
298
     * https://en.wikipedia.org/wiki/Gamma_function
299
     * https://en.wikipedia.org/wiki/Particular_values_of_the_Gamma_function
300
     *
301
     * For positive integers:
302
     *  Γ(n) = (n - 1)!
303
     *
304
     * For half integers:
305
     *
306
     *             _   (2n)!
307
     * Γ(½ + n) = √π  -------
308
     *                 4ⁿ n!
309
     *
310
     * For real numbers: use Lanczos approximation
311
     *
312
     * Implementation notes:
313
     * The original MathPHP implementation was based on the textbook mathematical formula, but this led to numerical
314
     * issues with very large numbers. Last version with this implementation was v2.4.0.
315
     *
316
     * The current implementation is heavily inspired by the R language's C implementation of gammafn, which itself is
317
     * a translation of a Fortran subroutine by W. Fullerton of Los Alamos Scientific Laboratory.
318
     * It can be considered a reimplementation in PHP.
319
     * R Project for Statistical Computing: https://www.r-project.org/
320
     * R Source: https://svn.r-project.org/R/
321
     *
322
     * @param float $x
323
     *
324
     * @return float
325
     *
326
     * @throws Exception\OutOfBoundsException
327
     * @throws Exception\NanException
328
     */
329
    public static function gamma(float $x)
330
    {
331
        static $gamcs = [
1,344✔
332
            +.8571195590989331421920062399942e-2,
333
            +.4415381324841006757191315771652e-2,
334
            +.5685043681599363378632664588789e-1,
335
            -.4219835396418560501012500186624e-2,
336
            +.1326808181212460220584006796352e-2,
337
            -.1893024529798880432523947023886e-3,
338
            +.3606925327441245256578082217225e-4,
339
            -.6056761904460864218485548290365e-5,
340
            +.1055829546302283344731823509093e-5,
341
            -.1811967365542384048291855891166e-6,
342
            +.3117724964715322277790254593169e-7,
343
            -.5354219639019687140874081024347e-8,
344
            +.9193275519859588946887786825940e-9,
345
            -.1577941280288339761767423273953e-9,
346
            +.2707980622934954543266540433089e-10,
347
            -.4646818653825730144081661058933e-11,
348
            +.7973350192007419656460767175359e-12,
349
            -.1368078209830916025799499172309e-12,
350
            +.2347319486563800657233471771688e-13,
351
            -.4027432614949066932766570534699e-14,
352
            +.6910051747372100912138336975257e-15,
353
            -.1185584500221992907052387126192e-15,
354
            +.2034148542496373955201026051932e-16,
355
            -.3490054341717405849274012949108e-17,
356
            +.5987993856485305567135051066026e-18,
357
            -.1027378057872228074490069778431e-18,
358
            +.1762702816060529824942759660748e-19,
359
            -.3024320653735306260958772112042e-20,
360
            +.5188914660218397839717833550506e-21,
361
            -.8902770842456576692449251601066e-22,
362
            +.1527474068493342602274596891306e-22,
363
            -.2620731256187362900257328332799e-23,
364
            +.4496464047830538670331046570666e-24,
365
            -.7714712731336877911703901525333e-25,
366
            +.1323635453126044036486572714666e-25,
367
            -.2270999412942928816702313813333e-26,
368
            +.3896418998003991449320816639999e-27,
369
            -.6685198115125953327792127999999e-28,
370
            +.1146998663140024384347613866666e-28,
371
            -.1967938586345134677295103999999e-29,
372
            +.3376448816585338090334890666666e-30,
373
            -.5793070335782135784625493333333e-31
374
        ];
375

376
        static $ngam  = 22;
1,344✔
377
        static $xmin  = -170.5674972726612;
1,344✔
378
        static $xmax  = 171.61447887182298;
1,344✔
379
        static $xsml  = 2.2474362225598545e-308;
1,344✔
380
        static $dxrel = 1.490116119384765696e-8;
1,344✔
381

382
        if (\is_nan($x)) {
1,344✔
383
            throw new Exception\NanException("gamma cannot compute x when NAN");
1✔
384
        }
385

386
        // Undefined (NAN) if x ≤ 0
387
        if ($x == 0 || ($x < 0 && $x == \round($x))) {
1,343✔
388
            throw new Exception\NanException("gamma undefined for x of $x");
9✔
389
        }
390

391
        $y = \abs($x);
1,334✔
392

393
        // Compute gamma for -10 ≤ x ≤ 10
394
        if ($y <= 10) {
1,334✔
395
            // First reduce the interval and find gamma(1 + y) for 0 ≤ y < 1
396
            $n = (int) $x;
1,303✔
397
            if ($x < 0) {
1,303✔
398
                --$n;
14✔
399
            }
400
            $y = $x - $n; // n = floor(x) ==> y in [0, 1)
1,303✔
401
            --$n;
1,303✔
402
            $value = self::chebyshevEval($y * 2 - 1, $gamcs, $ngam) + .9375;
1,303✔
403
            if ($n == 0) {
1,303✔
404
                return $value; // x = 1.dddd = 1+y
266✔
405
            }
406

407
            // Compute gamma(x) for -10 ≤ x < 1
408
            // Exactly 0 or -n was checked above already
409
            if ($n < 0) {
1,224✔
410
                // The argument is so close to 0 that the result would overflow.
411
                if ($y < $xsml) {
423✔
412
                    return \INF;
1✔
413
                }
414

415
                $n = -$n;
422✔
416
                for ($i = 0; $i < $n; $i++) {
422✔
417
                    $value /= ($x + $i);
422✔
418
                }
419
                return $value;
422✔
420
            }
421

422
            // gamma(x) for 2 ≤ x ≤ 10
423
            for ($i = 1; $i <= $n; $i++) {
976✔
424
                $value *= ($y + $i);
976✔
425
            }
426
            return $value;
976✔
427
        }
428
        // gamma(x) for y = |x| > 10.
429

430
        // Overflow (INF is the best answer)
431
        if ($x > $xmax) {
100✔
432
            return \INF;
1✔
433
        }
434

435
        // Underflow (0 is the best answer)
436
        if ($x < $xmin) {
99✔
437
            return 0;
1✔
438
        }
439

440
        if ($y <= 50 && $y == (int) $y) {
98✔
441
            $value = Combinatorics::factorial((int) $y - 1);
67✔
442
        } else { // Normal case
443
            $M_LN_SQRT_2PI = (\M_LNPI + \M_LN2) / 2;
48✔
444
            $value = \exp(($y - 0.5) * \log($y) - $y + $M_LN_SQRT_2PI + ((2*$y == (int)2*$y)? self::stirlingError($y) : self::logGammaCorr($y)));
48✔
445
        }
446

447
        if ($x > 0) {
98✔
448
            return $value;
96✔
449
        }
450

451
        // The answer is less than half precision because the argument is too near a negative integer.
452
        if (\abs(($x - (int)($x - 0.5)) / $x) < $dxrel) {
2✔
453
            // Just move on.
454
        }
455

456
        $sinpiy = \sin(\M_PI * $y);
2✔
457
        return -\M_PI / ($y * $sinpiy * $value);
2✔
458
    }
459

460
    /**
461
     * Log Gamma
462
     *
463
     * The implementation is heavily inspired by the R language's C implementation of lgammafn, which itself is
464
     * a translation of a Fortran subroutine by W. Fullerton of Los Alamos Scientific Laboratory.
465
     * It can be considered a reimplementation in PHP.
466
     * R Project for Statistical Computing: https://www.r-project.org/
467
     * R Source: https://svn.r-project.org/R/
468
     *
469
     * @param float $x
470
     *
471
     * @return float|int
472
     *
473
     * @throws Exception\NanException
474
     * @throws Exception\OutOfBoundsException
475
     */
476
    public static function logGamma(float $x)
477
    {
478
        static $xmax = 2.5327372760800758e+305;
718✔
479

480
        if (\is_nan($x)) {
718✔
481
            throw new Exception\NanException("Cannot compute logGamma when x is NAN");
1✔
482
        }
483

484
        // Negative integer argument
485
        if ($x <= 0 && $x == (int) $x) {
717✔
486
            return \INF; // INF is the best answer
3✔
487
        }
488

489
        $y = \abs($x);
714✔
490

491
        if ($y < 1e-306) {
714✔
492
            return -\log($y); // Denormalized range
2✔
493
        }
494
        if ($y <= 10) {
713✔
495
            return \log(abs(self::gamma($x)));
700✔
496
        }
497

498
        // From this point, y = |x| > 10
499

500
        if ($y > $xmax) {
287✔
501
            return \INF; // INF is the best answer
1✔
502
        }
503

504
        // y = x > 10
505
        if ($x > 0) {
286✔
506
            if ($x > 1e17) {
285✔
507
                return $x * (\log($x) - 1);
1✔
508
            }
509
            $M_LN_SQRT_2PI = (\M_LNPI + \M_LN2) / 2;
284✔
510
            if ($x > 4934720) {
284✔
511
                return($M_LN_SQRT_2PI + ($x - 0.5) * \log($x) - $x);
1✔
512
            }
513
            return $M_LN_SQRT_2PI + ($x - 0.5) * \log($x) - $x + self::logGammaCorr($x);
283✔
514
        }
515

516
        $M_LN_SQRT_PId2 = 0.225791352644727432363097614947; // log(sqrt(pi/2))
1✔
517
        $sinpiy = \abs(sin(\M_PI * $y));
1✔
518
        return $M_LN_SQRT_PId2 + ($x - 0.5) * \log($y) - $x - \log($sinpiy) - self::logGammaCorr($y);
1✔
519
    }
520

521
    /**
522
     * Beta function convenience method
523
     *
524
     * @param  float $x
525
     * @param  float $y
526
     *
527
     * @return float
528
     *
529
     * @throws Exception\OutOfBoundsException
530
     */
531
    public static function β(float $x, float $y): float
532
    {
533
        return self::beta($x, $y);
52✔
534
    }
535

536
    /**
537
     * Beta function
538
     *
539
     * https://en.wikipedia.org/wiki/Beta_function
540
     *
541
     * Selects the best beta algorithm for the provided values
542
     *
543
     * @param  float $a
544
     * @param  float $b
545
     *
546
     * @return float
547
     *
548
     * @throws Exception\OutOfBoundsException
549
     * @throws Exception\NanException
550
     */
551
    public static function beta(float $a, float $b): float
552
    {
553
        static $xmax = 171.61447887182298;
335✔
554

555
        if (\is_nan($a) || \is_nan($b)) {
335✔
556
            throw new Exception\NanException("Cannot compute beta when a or b is NAN: got a:$a, b:$b");
1✔
557
        }
558
        if ($a < 0 || $b < 0) {
334✔
559
            throw new Exception\OutOfBoundsException("a and b must be non-negative for beta: got a:$a, b:$b");
1✔
560
        }
561
        if ($a == 0 || $b == 0) {
333✔
562
            return \INF;
5✔
563
        }
564
        if (\is_infinite($a) || \is_infinite($b)) {
328✔
565
            return 0;
2✔
566
        }
567

568
        if ($a + $b < $xmax) {
326✔
569
            return self::betaBasic($a, $b);
325✔
570
        }
571

572
        $val = self::logBeta($a, $b);
1✔
573
        return \exp($val);
1✔
574
    }
575

576
    /**
577
     * Beta function
578
     *
579
     * https://en.wikipedia.org/wiki/Beta_function
580
     *
581
     *           Γ(x)Γ(y)
582
     * β(x, y) = --------
583
     *           Γ(x + y)
584
     *
585
     * @param  float $x
586
     * @param  float $y
587
     *
588
     * @return float
589
     *
590
     * @throws Exception\OutOfBoundsException
591
     */
592
    private static function betaBasic(float $x, float $y): float
593
    {
594
        $Γ⟮x⟯  = self::gamma($x);
325✔
595
        $Γ⟮y⟯  = self::gamma($y);
325✔
596
        $Γ⟮x + y⟯ = self::gamma($x + $y);
325✔
597

598
        return 1 / $Γ⟮x + y⟯ * $Γ⟮x⟯ * $Γ⟮y⟯;
325✔
599
    }
600

601
    /**
602
     * The log of the beta function
603
     *
604
     * The implementation is heavily inspired by the R language's C implementation of lbeta, which itself is
605
     * a translation of a Fortran subroutine by W. Fullerton of Los Alamos Scientific Laboratory.
606
     * It can be considered a reimplementation in PHP.
607
     * R Project for Statistical Computing: https://www.r-project.org/
608
     * R Source: https://svn.r-project.org/R/
609
     *
610
     * @param  float $a
611
     * @param  float $b
612
     *
613
     * @return float
614
     *
615
     * @throws Exception\OutOfBoundsException
616
     * @throws Exception\NanException
617
     */
618
    public static function logBeta(float $a, float $b): float
619
    {
620
        if (\is_nan($a) || \is_nan($b)) {
17✔
621
            throw new Exception\NanException("Cannot compute logBeta if a or b is NAN: got a:$a, b:$b");
1✔
622
        }
623

624
        $p = $a;
16✔
625
        $q = $a;
16✔
626
        if ($b < $p) {
16✔
627
            $p = $b;  // min(a,b)
5✔
628
        }
629
        if ($b > $q) {
16✔
630
            $q = $b;  // max(a,b)
7✔
631
        }
632

633
        // Both arguments must be >= 0
634
        if ($p < 0) {
16✔
635
            throw new Exception\OutOfBoundsException("p must be non-negative at this point of logBeta calculation: got $p");
1✔
636
        }
637
        if ($p == 0) {
15✔
638
            return \INF;
3✔
639
        }
640
        if (\is_infinite($q)) {
12✔
641
            return -\INF;
1✔
642
        }
643

644
        if ($p >= 10) {
11✔
645
            // p and q are big.
646
            $corr = self::logGammaCorr($p) + self::logGammaCorr($q) - self::logGammaCorr($p + $q);
2✔
647
            $M_LN_SQRT_2PI = (\M_LNPI + \M_LN2) / 2;
2✔
648
            return \log($q) * -0.5 + $M_LN_SQRT_2PI + $corr + ($p - 0.5) * \log($p / ($p + $q)) + $q * \log1p(-$p / ($p + $q));
2✔
649
        }
650
        if ($q >= 10) {
9✔
651
            // p is small, but q is big.
652
            $corr = self::logGammaCorr($q) - self::logGammaCorr($p + $q);
2✔
653
            return self::logGamma($p) + $corr + $p - $p * \log($p + $q) + ($q - 0.5) * \log1p(-$p / ($p + $q));
2✔
654
        }
655
        // p and q are small: p <= q < 10. */
656
        if ($p < 1e-306) {
7✔
657
            return self::logGamma($p) + (self::logGamma($q) - self::logGamma($p+$q));
1✔
658
        }
659
        return \log(self::beta($p, $q));
6✔
660
    }
661

662
    /**
663
     * Log gamma correction
664
     *
665
     * Compute the log gamma correction factor for x >= 10 so that
666
     * log(gamma(x)) = .5*log(2*pi) + (x-.5)*log(x) -x + lgammacor(x)
667
     *
668
     * The implementation is heavily inspired by the R language's C implementation of lgammacor, which itself is
669
     * a translation of a Fortran subroutine by W. Fullerton of Los Alamos Scientific Laboratory.
670
     * It can be considered a reimplementation in PHP.
671
     * R Project for Statistical Computing: https://www.r-project.org/
672
     * R Source: https://svn.r-project.org/R/
673
     *
674
     * @param float $x
675
     *
676
     * @return float
677
     */
678
    public static function logGammaCorr(float $x): float
679
    {
680
        static $algmcs = [
289✔
681
            +.1666389480451863247205729650822e+0,
682
            -.1384948176067563840732986059135e-4,
683
            +.9810825646924729426157171547487e-8,
684
            -.1809129475572494194263306266719e-10,
685
            +.6221098041892605227126015543416e-13,
686
            -.3399615005417721944303330599666e-15,
687
            +.2683181998482698748957538846666e-17,
688
            -.2868042435334643284144622399999e-19,
689
            +.3962837061046434803679306666666e-21,
690
            -.6831888753985766870111999999999e-23,
691
            +.1429227355942498147573333333333e-24,
692
            -.3547598158101070547199999999999e-26,
693
            +.1025680058010470912000000000000e-27,
694
            -.3401102254316748799999999999999e-29,
695
            +.1276642195630062933333333333333e-30,
696
        ];
697

698
        /**
699
         * For IEEE double precision DBL_EPSILON = 2^-52 = 2.220446049250313e-16 :
700
         * xbig = 2 ^ 26.5
701
         * xmax = DBL_MAX / 48 =  2^1020 / 3
702
         */
703
        static $nalgm = 5;
289✔
704
        static $xbig  = 94906265.62425156;
289✔
705
        static $xmax  = 3.745194030963158e306;
289✔
706

707
        if ($x < 10) {
289✔
708
            throw new Exception\OutOfBoundsException("x cannot be < 10: got $x");
1✔
709
        }
710
        if ($x >= $xmax) {
288✔
711
            // allow to underflow below
712
        } elseif ($x < $xbig) {
288✔
713
            $tmp = 10 / $x;
288✔
714
            return self::chebyshevEval($tmp * $tmp * 2 - 1, $algmcs, $nalgm) / $x;
288✔
715
        }
716
        return 1 / ($x * 12);
1✔
717
    }
718

719
    /**
720
     * Evaluate a Chebyshev Series with the Clenshaw Algorithm
721
     * https://en.wikipedia.org/wiki/Clenshaw_algorithm#Special_case_for_Chebyshev_series
722
     *
723
     * The implementation is inspired by the R language's C implementation of chebyshev_eval, which itself is
724
     * a translation of a Fortran subroutine by W. Fullerton of Los Alamos Scientific Laboratory.
725
     * It can be considered a reimplementation in PHP.
726
     *
727
     * @param float   $x
728
     * @param float[] $a
729
     * @param int     $n
730
     *
731
     * @return float
732
     *
733
     * @throws Exception\OutOfBoundsException
734
     */
735
    private static function chebyshevEval(float $x, array $a, int $n): float
736
    {
737
        if ($n < 1 || $n > 1000) {
1,319✔
738
            throw new Exception\OutOfBoundsException("n cannot be < 1 or > 1000: got $n");
2✔
739
        }
740
        if ($x < -1.1 || $x > 1.1) {
1,317✔
741
            throw new Exception\OutOfBoundsException("x cannot be < -1.1 or greater than 1.1: got $x");
2✔
742
        }
743

744
        $2x = $x * 2;
1,315✔
745
        [$b0, $b1, $b2] = [0, 0, 0];
1,315✔
746

747
        for ($i = 1; $i <= $n; $i++) {
1,315✔
748
            $b2 = $b1;
1,315✔
749
            $b1 = $b0;
1,315✔
750
            $b0 = $2x * $b1 - $b2 + $a[$n - $i];
1,315✔
751
        }
752
        return ($b0 - $b2) * 0.5;
1,315✔
753
    }
754

755
    /**
756
     * Multivariate Beta function
757
     * https://en.wikipedia.org/wiki/Beta_function#Multivariate_beta_function
758
     *
759
     *                     Γ(α₁)Γ(α₂) ⋯ Γ(αn)
760
     * B(α₁, α₂, ... αn) = ------------------
761
     *                      Γ(α₁ + α₂ ⋯ αn)
762
     *
763
     * @param float[] $αs
764
     *
765
     * @return float
766
     *
767
     * @throws Exception\OutOfBoundsException
768
     */
769
    public static function multivariateBeta(array $αs): float
770
    {
771
        foreach ($αs as $α) {
66✔
772
            if ($α == 0) {
66✔
773
                return \INF;
5✔
774
            }
775
        }
776

777
        static $xmax = 171.61447887182298;
61✔
778

779
        $∑α = \array_sum($αs);
61✔
780
        if ($∑α == \INF) {
61✔
781
            return 0;
2✔
782
        }
783
        if ($∑α < $xmax) {  // ~= 171.61 for IEEE
59✔
784
            $Γ⟮∑α⟯ = self::Γ($∑α);
58✔
785
            $∏= 1 / $Γ⟮∑α⟯;
58✔
786
            foreach ($αs as $α) {
58✔
787
                $∏ *= self::Γ($α);
58✔
788
            }
789

790
            return $∏;
58✔
791
        }
792

793
        $∑ = -self::logGamma($∑α);
1✔
794
        foreach ($αs as $α) {
1✔
795
            $∑ += self::logGamma($α);
1✔
796
        }
797
        return \exp($∑);
1✔
798
    }
799

800
    /**
801
     * Logistic function (logistic sigmoid function)
802
     * A logistic function or logistic curve is a common "S" shape (sigmoid curve).
803
     * https://en.wikipedia.org/wiki/Logistic_function
804
     *
805
     *             L
806
     * f(x) = -----------
807
     *        1 + ℯ⁻ᵏ⁽ˣ⁻ˣ⁰⁾
808
     *
809
     *
810
     * @param float $x₀ x-value of the sigmoid's midpoint
811
     * @param float $L  the curve's maximum value
812
     * @param float $k  the steepness of the curve
813
     * @param float $x
814
     *
815
     * @return float
816
     */
817
    public static function logistic(float $x₀, float $L, float $k, float $x): float
818
    {
819
        $ℯ⁻ᵏ⁽ˣ⁻ˣ⁰⁾ = \exp(-$k * ($x - $x₀));
12✔
820

821
        return $L / (1 + $ℯ⁻ᵏ⁽ˣ⁻ˣ⁰⁾);
12✔
822
    }
823

824
    /**
825
     * Sigmoid function
826
     * A sigmoid function is a mathematical function having an "S" shaped curve (sigmoid curve).
827
     * Often, sigmoid function refers to the special case of the logistic function
828
     * https://en.wikipedia.org/wiki/Sigmoid_function
829
     *
830
     *           1
831
     * S(t) = -------
832
     *        1 + ℯ⁻ᵗ
833
     *
834
     * @param  float $t
835
     *
836
     * @return float
837
     */
838
    public static function sigmoid(float $t): float
839
    {
840
        $ℯ⁻ᵗ = \exp(-$t);
27✔
841

842
        return 1 / (1 + $ℯ⁻ᵗ);
27✔
843
    }
844

845
    /**
846
     * Error function (Gauss error function)
847
     * https://en.wikipedia.org/wiki/Error_function
848
     *
849
     * This is an approximation of the error function (maximum error: 1.5×10−7)
850
     *
851
     * erf(x) ≈ 1 - (a₁t + a₂t² + a₃t³ + a₄t⁴ + a₅t⁵)ℯ^-x²
852
     *
853
     *       1
854
     * t = ------
855
     *     1 + px
856
     *
857
     * p = 0.3275911
858
     * a₁ = 0.254829592, a₂ = −0.284496736, a₃ = 1.421413741, a₄ = −1.453152027, a₅ = 1.061405429
859
     *
860
     * @param  float $x
861
     *
862
     * @return float
863
     */
864
    public static function errorFunction(float $x): float
865
    {
866
        if ($x == 0) {
520✔
867
            return 0;
223✔
868
        }
869

870
        $p  = 0.3275911;
489✔
871
        $t  = 1 / ( 1 + $p * \abs($x) );
489✔
872

873
        $a₁ = 0.254829592;
489✔
874
        $a₂ = -0.284496736;
489✔
875
        $a₃ = 1.421413741;
489✔
876
        $a₄ = -1.453152027;
489✔
877
        $a₅ = 1.061405429;
489✔
878

879
        $error = 1 - ( $a₁ * $t + $a₂ * $t ** 2 + $a₃ * $t ** 3 + $a₄ * $t ** 4 + $a₅ * $t ** 5 ) * \exp(-\abs($x) ** 2);
489✔
880

881
        return ( $x > 0 ) ? $error : -$error;
489✔
882
    }
883

884
    /**
885
     * Error function (Gauss error function)
886
     * Convenience method for errorFunction
887
     *
888
     * @param  float $x
889
     *
890
     * @return float
891
     */
892
    public static function erf(float $x): float
893
    {
894
        return self::errorFunction($x);
520✔
895
    }
896

897
    /**
898
     * Complementary error function (erfc)
899
     * erfc(x) ≡ 1 - erf(x)
900
     *
901
     * @param  int|float $x
902
     *
903
     * @return float
904
     */
905
    public static function complementaryErrorFunction($x): float
906
    {
907
        return 1 - self::erf($x);
17✔
908
    }
909

910
    /**
911
     * Complementary error function (erfc)
912
     * Convenience method for complementaryErrorFunction
913
     *
914
     * @param  float $x
915
     *
916
     * @return float
917
     */
918
    public static function erfc(float $x): float
919
    {
920
        return 1 - self::erf($x);
17✔
921
    }
922

923
    /**
924
     * Upper Incomplete Gamma Function - Γ(s,x)
925
     * https://en.wikipedia.org/wiki/Incomplete_gamma_function
926
     *
927
     * @param float $s shape parameter > 0
928
     * @param float $x lower limit of integration
929
     *
930
     * @return float
931
     *
932
     * @throws Exception\OutOfBoundsException if s is ≤ 0
933
     */
934
    public static function upperIncompleteGamma(float $s, float $x): float
935
    {
936
        if ($s <= 0) {
18✔
937
            throw new Exception\OutOfBoundsException("S must be > 0. S = $s");
1✔
938
        }
939
        return self::gamma($s) - self::lowerIncompleteGamma($s, $x);
17✔
940
    }
941

942
    /**
943
     * Lower incomplete gamma function - γ(s,t)
944
     * https://en.wikipedia.org/wiki/Incomplete_gamma_function#Lower_incomplete_Gamma_function
945
     *
946
     * This function is exact for all integer multiples of .5
947
     * using the recurrence relation: γ⟮s+1,x⟯= s*γ⟮s,x⟯-xˢ*e⁻ˣ
948
     *
949
     * The function can be evaluated at other points using the series:
950
     *              zˢ     /      x          x²             x³            \
951
     * γ(s,x) =  -------- | 1 + ----- + ---------- + --------------- + ... |
952
     *            s * eˣ   \     s+1    (s+1)(s+2)   (s+1)(s+2)(s+3)      /
953
     *
954
     * @param float $s > 0
955
     * @param float $x ≥ 0
956
     *
957
     * @return float
958
     */
959
    public static function lowerIncompleteGamma(float $s, float $x): float
960
    {
961
        if ($x == 0) {
132✔
962
            return 0;
10✔
963
        }
964
        if ($s == 0) {
122✔
965
            return \NAN;
4✔
966
        }
967

968
        if ($s == 1) {
118✔
969
            return 1 - \exp(-1 * $x);
59✔
970
        }
971
        if ($s == .5) {
95✔
972
            $√π = \sqrt(\M_PI);
48✔
973
            $√x = \sqrt($x);
48✔
974
            return $√π * self::erf($√x);
48✔
975
        }
976
        if (\round($s * 2, 0) == $s * 2) {
72✔
977
            return ($s - 1) * self::lowerIncompleteGamma($s - 1, $x) - $x ** ($s - 1) * \exp(-1 * $x);
61✔
978
        }
979

980
        $tol       = .000000000001;
11✔
981
        $xˢ∕s∕eˣ   = $x ** $s / \exp($x) / $s;
11✔
982
        $sum       = 1;
11✔
983
        $fractions = [];
11✔
984
        $element   = 1 + $tol;
11✔
985

986
        while ($element > $tol) {
11✔
987
            $fractions[] = $x / ++$s;
11✔
988
            $element     = \array_product($fractions);
11✔
989
            $sum        += $element;
11✔
990
        }
991

992
        return $xˢ∕s∕eˣ * $sum;
11✔
993
    }
994

995
    /**
996
     * γ - Convenience method for lower incomplete gamma function
997
     * https://en.wikipedia.org/wiki/Incomplete_gamma_function#Lower_incomplete_Gamma_function
998
     *
999
     * @param float $s > 0
1000
     * @param float $x ≥ 0
1001
     *
1002
     * @return float
1003
     */
1004
    public static function γ(float $s, float $x): float
1005
    {
1006
        return self::lowerIncompleteGamma($s, $x);
64✔
1007
    }
1008

1009
    /**
1010
     * Regularized lower incomplete gamma function - P(s,x)
1011
     * https://en.wikipedia.org/wiki/Incomplete_gamma_function#Regularized_Gamma_functions_and_Poisson_random_variables
1012
     *
1013
     *          γ(s,x)
1014
     * P(s,x) = ------
1015
     *           Γ(s)
1016
     *
1017
     * P(s,x) is the cumulative distribution function for Gamma random variables with shape parameter s and scale parameter 1
1018
     *
1019
     *
1020
     * @param float $s > 0
1021
     * @param float $x ≥ 0
1022
     *
1023
     * @return float
1024
     *
1025
     * @throws Exception\OutOfBoundsException
1026
     */
1027
    public static function regularizedLowerIncompleteGamma(float $s, float $x): float
1028
    {
1029
        $γ⟮s、x⟯ = self::lowerIncompleteGamma($s, $x);
18✔
1030
        $Γ⟮s⟯    = self::gamma($s);
18✔
1031

1032
        return $γ⟮s、x⟯ / $Γ⟮s⟯;
18✔
1033
    }
1034

1035
    /**
1036
     * Incomplete Beta Function - B(x;a,b)
1037
     *
1038
     * Generalized form of the beta function
1039
     * https://en.wikipedia.org/wiki/Beta_function#Incomplete_beta_function
1040
     *
1041
     * @param float $x Upper limit of the integration 0 ≦ x ≦ 1
1042
     * @param float $a Shape parameter a > 0
1043
     * @param float $b Shape parameter b > 0
1044
     *
1045
     * @return float
1046
     *
1047
     * @throws Exception\BadDataException
1048
     * @throws Exception\BadParameterException
1049
     * @throws Exception\OutOfBoundsException
1050
     */
1051
    public static function incompleteBeta(float $x, float $a, float $b): float
1052
    {
1053
        return self::regularizedIncompleteBeta($x, $a, $b) * self::beta($a, $b);
23✔
1054
    }
1055

1056
    /**
1057
     * Regularized incomplete beta function - Iₓ(a, b)
1058
     *
1059
     * https://en.wikipedia.org/wiki/Beta_function#Incomplete_beta_function
1060
     *
1061
     * This function looks at the values of x, a, and b, and determines which algorithm is best to calculate
1062
     * the value of Iₓ(a, b)
1063
     *
1064
     * There are several ways to calculate the incomplete beta function (See: https://dlmf.nist.gov/8.17).
1065
     * This follows the continued fraction form, which consists of a term followed by a converging series of fractions.
1066
     * Lentz's Algorithm is used to solve the continued fraction.
1067
     *
1068
     * The implementation of the continued fraction using Lentz's Algorithm is heavily inspired by Lewis Van Winkle's
1069
     * reference implementation in C: https://github.com/codeplea/incbeta
1070
     *
1071
     * Other implementations used as references in the past:
1072
     *  http://www.boost.org/doc/libs/1_35_0/libs/math/doc/sf_and_dist/html/math_toolkit/special/sf_beta/ibeta_function.html
1073
     *  https://github.com/boostorg/math/blob/develop/include/boost/math/special_functions/beta.hpp
1074
     *
1075
     * @param float $x Upper limit of the integration 0 ≦ x ≦ 1
1076
     * @param float $a Shape parameter a > 0
1077
     * @param float $b Shape parameter b > 0
1078
     *
1079
     * @return float
1080
     *
1081
     * @throws Exception\BadDataException
1082
     * @throws Exception\BadParameterException
1083
     * @throws Exception\FunctionFailedToConvergeException
1084
     * @throws Exception\NanException
1085
     * @throws Exception\OutOfBoundsException
1086
     */
1087
    public static function regularizedIncompleteBeta(float $x, float $a, float $b): float
1088
    {
1089
        $limits = [
1090
            'x' => '[0, 1]',
917✔
1091
            'a' => '(0,∞)',
1092
            'b' => '(0,∞)',
1093
        ];
1094
        Support::checkLimits($limits, ['x' => $x, 'a' => $a, 'b' => $b]);
917✔
1095

1096
        if ($x == 1 || $x == 0) {
915✔
1097
            return $x;
263✔
1098
        }
1099

1100
        if ($a == 1) {
880✔
1101
            return 1 - (1 - $x) ** $b;
205✔
1102
        }
1103

1104
        if ($b == 1) {
712✔
1105
            return $x ** $a;
32✔
1106
        }
1107

1108
        if ($x > ($a + 1)/($a + $b + 2)) {
681✔
1109
            return 1 - self::regularizedIncompleteBeta((1 - $x), $b, $a);
438✔
1110
        }
1111

1112
        // Continued fraction using Lentz's Algorithm.
1113

1114
        $first_term = \exp(\log($x) * $a + \log(1.0 - $x) * $b - (self::logGamma($a) + self::logGamma($b) - self::logGamma($a + $b))) / $a;
681✔
1115

1116
        // PHP 7.2.0 offers PHP_FLOAT_EPSILON, but 1.0e-30 is used in Lewis Van Winkle's
1117
        // reference implementation to prevent division-by-zero errors, so we use the same here.
1118
        $ε = 1.0e-30;
681✔
1119

1120
        // These starting values are changed from the reference implementation to precalculate $i = 0 and avoid the
1121
        // extra conditional expression inside the loop.
1122
        $d = 1.0;
681✔
1123
        $c = 2.0;
681✔
1124
        $f = $c * $d;
681✔
1125

1126
        $m = 0;
681✔
1127
        for ($i = 1; $i <= 200; $i++) {
681✔
1128
            if ($i % 2 === 0) {
681✔
1129
                // Even term
1130
                $m++;
681✔
1131
                $numerator = ($m * ($b - $m) * $x) / (($a + 2.0 * $m - 1.0) * ($a + 2.0 * $m));
681✔
1132
            } else {
1133
                // Odd term
1134
                $numerator = -(($a + $m) * ($a + $b + $m) * $x) / (($a + 2.0 * $m) * ($a + 2.0 * $m + 1));
681✔
1135
            }
1136

1137
            // Lentz's Algorithm
1138
            $d = 1.0 + $numerator * $d;
681✔
1139
            $d = 1.0 / (\abs($d) < $ε ? $ε : $d);
681✔
1140
            $c = 1.0 + $numerator / (\abs($c) < $ε ? $ε : $c);
681✔
1141
            $f *= $c * $d;
681✔
1142

1143
            if (\abs(1.0 - $c * $d) < 1.0e-8) {
681✔
1144
                return $first_term * ($f - 1.0);
681✔
1145
            }
1146
        }
1147

1148
        // Series did not converge.
1149
        throw new Exception\FunctionFailedToConvergeException(sprintf('Continuous fraction series is not converging for x = %f, a = %f, b = %f', $x, $a, $b));
×
1150
    }
1151

1152

1153
    /**
1154
     * Generalized Hypergeometric Function
1155
     *
1156
     * https://en.wikipedia.org/wiki/Generalized_hypergeometric_function
1157
     *
1158
     *                                       ∞
1159
     *                                      ____
1160
     *                                      \     ∏ap⁽ⁿ⁾ * zⁿ
1161
     * pFq(a₁,a₂,...ap;b₁,b₂,...,bq;z)=      >    ------------
1162
     *                                      /      ∏bq⁽ⁿ⁾ * n!
1163
     *                                      ‾‾‾‾
1164
     *                                       n=0
1165
     *
1166
     * Where a⁽ⁿ⁾ is the Pochhammer Function or Rising Factorial
1167
     *
1168
     * We are evaluating this as a series:
1169
     *
1170
     *               (a + n - 1) * z
1171
     * ∏n = ∏n₋₁  * -----------------
1172
     *               (b + n - 1) * n
1173
     *
1174
     *                  n   (a + n - 1) * z
1175
     *   ₁F₁ = ₁F₁n₋₁ + ∏  -----------------  = ₁F₁n₋₁ + ∏n
1176
     *                  1   (b + n - 1) * n
1177
     *
1178
     * @param int    $p         the number of parameters in the numerator
1179
     * @param int    $q         the number of parameters in the denominator
1180
     * @param float  ...$params a collection of the a, b, and z parameters
1181
     *
1182
     * @return float
1183
     *
1184
     * @throws Exception\BadParameterException if the number of parameters is incorrect
1185
     */
1186
    public static function generalizedHypergeometric(int $p, int $q, float ...$params): float
1187
    {
1188
        $n = \count($params);
47✔
1189
        if ($n !== $p + $q + 1) {
47✔
1190
            $expected_num_params = $p + $q + 1;
1✔
1191
            throw new Exception\BadParameterException("Number of parameters is incorrect. Expected $expected_num_params; got $n");
1✔
1192
        }
1193

1194
        $a       = \array_slice($params, 0, $p);
46✔
1195
        $b       = \array_slice($params, $p, $q);
46✔
1196
        $z       = $params[$n - 1];
46✔
1197
        $tol     = .00000001;
46✔
1198
        $n       = 1;
46✔
1199
        $sum     = 0;
46✔
1200
        $product = 1;
46✔
1201

1202
        do {
1203
            $sum     += $product;
46✔
1204
            $a_sum    = \array_product(Single::add($a, $n - 1));
46✔
1205
            $b_sum    = \array_product(Single::add($b, $n - 1));
46✔
1206
            $product *= $a_sum * $z / $b_sum / $n;
46✔
1207
            $n++;
46✔
1208
        } while ($product / $sum > $tol);
46✔
1209

1210
        return $sum;
46✔
1211
    }
1212

1213
    /**
1214
     * Confluent Hypergeometric Function
1215
     *
1216
     * https://en.wikipedia.org/wiki/Confluent_hypergeometric_function
1217
     *         ∞
1218
     *        ____
1219
     *        \     a⁽ⁿ⁾ * zⁿ
1220
     *  ₁F₁ =  >    ---------
1221
     *        /     b⁽ⁿ⁾ * n!
1222
     *        ‾‾‾‾
1223
     *        n=0
1224
     *
1225
     * @param float $a the numerator value
1226
     * @param float $b the denominator value
1227
     * @param float $z
1228
     *
1229
     * @return float
1230
     *
1231
     * @throws Exception\BadParameterException
1232
     */
1233
    public static function confluentHypergeometric(float $a, float $b, float $z): float
1234
    {
1235
        return self::generalizedHypergeometric(1, 1, $a, $b, $z);
36✔
1236
    }
1237

1238
    /**
1239
     * Hypergeometric Function
1240
     *
1241
     * https://en.wikipedia.org/wiki/Hypergeometric_function
1242
     *         ∞
1243
     *        ____
1244
     *        \     a⁽ⁿ⁾ * b⁽ⁿ⁾ * zⁿ
1245
     *  ₂F₁ =  >    ----------------
1246
     *        /         c⁽ⁿ⁾ * n!
1247
     *        ‾‾‾‾
1248
     *        n=0
1249
     *
1250
     * @param float $a the first numerator value
1251
     * @param float $b the second numerator value
1252
     * @param float $c the denominator value
1253
     * @param float $z |z| < 1
1254
     *
1255
     * @return float
1256
     *
1257
     * @throws Exception\OutOfBoundsException if |z| >= 1
1258
     * @throws Exception\BadParameterException
1259
     */
1260
    public static function hypergeometric(float $a, float $b, float $c, float $z): float
1261
    {
1262
        if (\abs($z) >= 1) {
5✔
1263
             throw new Exception\OutOfBoundsException('|z| must be < 1. |z| = ' . \abs($z));
1✔
1264
        }
1265

1266
        return self::generalizedHypergeometric(2, 1, $a, $b, $c, $z);
4✔
1267
    }
1268

1269
    /**
1270
     * Softmax (normalized exponential)
1271
     * A generalization of the logistic function that "squashes" a K-dimensional
1272
     * vector z of arbitrary real values to a K-dimensional vector σ(z) of real values
1273
     * in the range (0, 1) that add up to 1.
1274
     * https://en.wikipedia.org/wiki/Softmax_function
1275
     *
1276
     *           ℯᶻⱼ
1277
     * σ(𝐳)ⱼ = ------  for j = 1 to K
1278
     *          ᴷ
1279
     *          ∑ ℯᶻᵢ
1280
     *         ⁱ⁼¹
1281
     *
1282
     * @param  float[] $𝐳
1283
     *
1284
     * @return float[]
1285
     */
1286
    public static function softmax(array $𝐳): array
1287
    {
1288
        $ℯ = \M_E;
15✔
1289

1290
        $∑ᴷℯᶻᵢ = \array_sum(\array_map(
15✔
1291
            function ($z) use ($ℯ) {
1292
                return $ℯ ** $z;
15✔
1293
            },
15✔
1294
            $𝐳
15✔
1295
        ));
1296

1297
        $σ⟮𝐳⟯ⱼ = \array_map(
15✔
1298
            function ($z) use ($ℯ, $∑ᴷℯᶻᵢ) {
1299
                return ($ℯ ** $z) / $∑ᴷℯᶻᵢ;
15✔
1300
            },
15✔
1301
            $𝐳
15✔
1302
        );
1303

1304
        return $σ⟮𝐳⟯ⱼ;
15✔
1305
    }
1306
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc