Failed Conditions
Push — dev ( b97a42...437690 )
by Jordan
05:47 queued 02:36
created

SeriesProvider   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Test Coverage

Coverage 66.67%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 56
dl 0
loc 151
ccs 36
cts 54
cp 0.6667
rs 10
c 1
b 0
f 0
wmc 13

2 Methods

Rating   Name   Duplication   Size   Complexity  
B maclaurinSeries() 0 77 10
A genericTwoPartSeries() 0 30 3
1
<?php
2
3
namespace Samsara\Fermat\Provider;
4
5
use Samsara\Exceptions\UsageError\IntegrityConstraint;
6
use Samsara\Exceptions\UsageError\OptionalExit;
7
use Samsara\Fermat\Numbers;
8
use Samsara\Fermat\Types\Base\Interfaces\Numbers\DecimalInterface;
9
use Samsara\Fermat\Types\Base\Interfaces\Numbers\NumberInterface;
10
use Samsara\Fermat\Types\Base\Interfaces\Numbers\SimpleNumberInterface;
11
use Samsara\Fermat\Values\ImmutableDecimal;
12
13
class SeriesProvider
14
{
15
16
    /**
17
     * Creates a series that evaluates the following:
18
     *
19
     * SUM[$startTerm -> infinity](
20
     *  $numerator($n) × $input^$exponent($n)
21
     *  --------------------------------
22
     *          $denominator($n)
23
     * )
24
     *
25
     * Where $n is the current term number, starting at $startTerm, and increasing by 1 each loop; where $numerator,
26
     * $exponent, and $denominator are callables that take the term number (as an int) as their only input, and give the
27
     * value of that section at that term number; and where $input is the x value being considered for the series.
28
     *
29
     * The function continues adding terms until a term has MORE leading zeros than the $precision setting. (That is,
30
     * until it adds zero to the total when considering significant digits.)
31
     *
32
     * @param SimpleNumberInterface $input
33
     * @param callable              $numerator
34
     * @param callable              $exponent
35
     * @param callable              $denominator
36
     * @param int                   $startTermAt
37
     * @param int                   $precision
38
     * @param int                   $consecutiveDivergeLimit
39
     * @param int                   $totalDivergeLimit
40
     *
41
     * @return ImmutableDecimal
42
     * @throws IntegrityConstraint
43
     * @throws OptionalExit
44
     */
45 19
    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)
54
    {
55
56 19
        $sum = Numbers::makeZero($precision);
57 19
        $value = Numbers::make(Numbers::IMMUTABLE, $input->getValue(), $precision);
58
59 19
        $continue = true;
60 19
        $termNumber = $startTermAt;
61
62 19
        $adjustmentOfZero = 0;
63 19
        $prevDiff = Numbers::makeZero($precision);
64 19
        $prevSum = $sum;
65 19
        $divergeCount = -1;
66 19
        $persistentDivergeCount = -1;
67 19
        $currentPrecision = 0;
68
69 19
        while ($continue) {
70 19
            $term = Numbers::makeOne($precision);
71
72
            try {
73 19
                $exTerm = $value->pow($exponent($termNumber));
74 19
                $term = $term->multiply($exTerm);
75 19
                $term = $term->divide($denominator($termNumber));
76 19
                $term = $term->multiply($numerator($termNumber));
77
            } catch (IntegrityConstraint $constraint) {
78
                return $sum->truncateToPrecision($currentPrecision+1);
79
            }
80
81
            /** @var ImmutableDecimal $term */
82 19
            if ($term->numberOfLeadingZeros() >= $precision && !$term->isWhole()) {
83 12
                $continue = false;
84
            }
85
86 19
            $currentPrecision = $term->numberOfLeadingZeros();
87
88 19
            if ($term->isEqual(0)) {
89 8
                $adjustmentOfZero++;
90
            } else {
91 19
                $adjustmentOfZero = 0;
92
            }
93
94 19
            if ($adjustmentOfZero > 5) {
95 8
                $continue = false;
96
            }
97
98 19
            $sum = $sum->add($term);
99 19
            $currDiff = $sum->subtract($prevSum)->abs();
100
101 19
            if ($prevDiff->isLessThan($currDiff)) {
102 19
                $divergeCount++;
103 19
                $persistentDivergeCount++;
104
            } else {
105 19
                $divergeCount = 0;
106
            }
107
108 19
            if ($divergeCount == $consecutiveDivergeLimit || $persistentDivergeCount == $totalDivergeLimit) {
109
                throw new OptionalExit(
110
                    'Series appear to be diverging. Current diverge count: '.$divergeCount.' | Persistent diverge count: '.$persistentDivergeCount,
111
                    'A call was made to SeriesProvider::maclaurinSeries() that seems to be diverging. Exiting the loop.'
112
                );
113
            }
114
115 19
            $prevDiff = $currDiff;
116 19
            $prevSum = $sum;
117
118 19
            $termNumber++;
119
        }
120
121 19
        return $sum->roundToPrecision($precision);
122
123
    }
124
125
    /**
126
     * @param callable $part1
127
     * @param callable $part2
128
     * @param callable $exponent
129
     * @param int $startTermAt
130
     * @param int $precision
131
     *
132
     * @return ImmutableDecimal
133
     */
134
    public static function genericTwoPartSeries(
135
        callable $part1,
136
        callable $part2,
137
        callable $exponent,
138
        int $startTermAt = 0,
139
        int $precision = 10): ImmutableDecimal
140
    {
141
142
        $x = Numbers::makeZero($precision+1);
143
144
        $continue = true;
145
        $termNumber = $startTermAt;
146
147
        while ($continue) {
148
            $term = Numbers::makeOne($precision+1);
149
150
            /** @var ImmutableDecimal $term */
151
            $term = $term->multiply($part2($termNumber))->pow($exponent($termNumber))
152
                ->multiply($part1($termNumber));
153
154
            if ($term->numberOfLeadingZeros()-1 >= $precision) {
155
                $continue = false;
156
            }
157
158
            $x = $x->add($term);
159
160
            $termNumber++;
161
        }
162
163
        return $x->roundToPrecision($precision+1);
164
165
    }
166
    
167
}