Failed Conditions
Pull Request — dev (#50)
by Jordan
06:20
created

SeriesProvider::genericTwoPartSeries()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 30
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 3
nop 5
dl 0
loc 30
ccs 0
cts 13
cp 0
crap 12
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
namespace Samsara\Fermat\Provider;
4
5
use ReflectionException;
6
use Samsara\Exceptions\UsageError\IntegrityConstraint;
7
use Samsara\Exceptions\UsageError\OptionalExit;
8
use Samsara\Fermat\Numbers;
9
use Samsara\Fermat\Types\Base\Interfaces\Numbers\SimpleNumberInterface;
10
use Samsara\Fermat\Values\ImmutableDecimal;
11
12
class SeriesProvider
13
{
14
15
    /**
16
     * Creates a series that evaluates the following:
17
     *
18
     * SUM[$startTerm -> infinity](
19
     *  $numerator($n) × $input^$exponent($n)
20
     *  --------------------------------
21
     *          $denominator($n)
22
     * )
23
     *
24
     * Where $n is the current term number, starting at $startTerm, and increasing by 1 each loop; where $numerator,
25
     * $exponent, and $denominator are callables that take the term number (as an int) as their only input, and give the
26
     * value of that section at that term number; and where $input is the x value being considered for the series.
27
     *
28
     * The function continues adding terms until a term has MORE leading zeros than the $precision setting. (That is,
29
     * until it adds zero to the total when considering significant digits.)
30
     *
31
     * @param SimpleNumberInterface $input
32
     * @param callable $numerator
33
     * @param callable $exponent
34
     * @param callable $denominator
35
     * @param int $startTermAt
36
     * @param int $precision
37
     * @param int $consecutiveDivergeLimit
38
     * @param int $totalDivergeLimit
39
     *
40
     * @return ImmutableDecimal
41
     * @throws IntegrityConstraint
42
     * @throws OptionalExit
43
     * @throws ReflectionException
44
     */
45 28
    public static function maclaurinSeries(
46
        SimpleNumberInterface $input, // x value in series
47
        callable $numerator, // a function determining what the sign (+/-) is at the nth term
48
        callable $exponent, // a function determining the exponent of x at the nth term
49
        callable $denominator, // a function determining the denominator at the nth term
50
        int $startTermAt = 0,
51
        int $precision = 10,
52
        int $consecutiveDivergeLimit = 5,
53
        int $totalDivergeLimit = 10): ImmutableDecimal
54
    {
55
56 28
        ++$precision;
57
58 28
        $sum = Numbers::makeZero($precision);
59 28
        $value = Numbers::make(Numbers::IMMUTABLE, $input->getValue(), $precision);
60
61 28
        $continue = true;
62 28
        $termNumber = $startTermAt;
63
64 28
        $adjustmentOfZero = 0;
65 28
        $prevDiff = Numbers::makeZero($precision);
66 28
        $prevSum = $sum;
67 28
        $divergeCount = -1;
68 28
        $persistentDivergeCount = -1;
69 28
        $currentPrecision = 0;
70
71 28
        while ($continue) {
72 28
            $term = Numbers::makeOne($precision);
73
74
            try {
75 28
                $exTerm = $value->pow($exponent($termNumber));
76 28
                $term = $term->multiply($exTerm);
77 28
                $term = $term->divide($denominator($termNumber));
78 28
                $term = $term->multiply($numerator($termNumber));
79
            } catch (IntegrityConstraint $constraint) {
80
                return $sum->truncateToPrecision($currentPrecision+1);
81
            }
82
83
            /** @var ImmutableDecimal $term */
84 28
            if ($term->numberOfLeadingZeros() >= $precision && !$term->isWhole()) {
85 22
                $continue = false;
86
            }
87
88 28
            $currentPrecision = $term->numberOfLeadingZeros();
89
90 28
            if ($term->isEqual(0)) {
91 6
                $adjustmentOfZero++;
92
            } else {
93 28
                $adjustmentOfZero = 0;
94
            }
95
96 28
            if ($adjustmentOfZero > 15) {
97 6
                $continue = false;
98
            }
99
100 28
            /** @var ImmutableDecimal $sum */
101 28
            $sum = $sum->add($term);
102
            $currDiff = $sum->subtract($prevSum)->abs();
103
104
            if ($prevDiff->isLessThan($currDiff)) {
105
                $divergeCount++;
106
                $persistentDivergeCount++;
107
            } else {
108
                $divergeCount = 0;
109
            }
110
111
            if ($divergeCount === $consecutiveDivergeLimit || $persistentDivergeCount === $totalDivergeLimit) {
112 28
                throw new OptionalExit(
113
                    'Series appear to be diverging. Current diverge count: '.$divergeCount.' | Persistent diverge count: '.$persistentDivergeCount,
114
                    'A call was made to SeriesProvider::maclaurinSeries() that seems to be diverging. Exiting the loop.'
115
                );
116
            }
117
118
            $prevDiff = $currDiff;
119 28
            $prevSum = $sum;
120 28
121
            $termNumber++;
122 28
        }
123
124
        return $sum->roundToPrecision($precision);
125 28
126
    }
127
    
128
}