Failed Conditions
Pull Request — master (#47)
by Jordan
06:42 queued 03:44
created

SeriesProvider   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 155
Duplicated Lines 0 %

Test Coverage

Coverage 64.71%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 52
dl 0
loc 155
ccs 33
cts 51
cp 0.6471
rs 10
c 1
b 0
f 0
wmc 12

2 Methods

Rating   Name   Duplication   Size   Complexity  
B maclaurinSeries() 0 81 9
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 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)
54
    {
55
56 28
        $precision += 1;
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);
0 ignored issues
show
Unused Code introduced by
The assignment to $prevDiff is dead and can be removed.
Loading history...
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 21
                $continue = false;
86
            }
87
88 28
            $currentPrecision = $term->numberOfLeadingZeros();
89
90 28
            if ($term->isEqual(0)) {
91 7
                $adjustmentOfZero++;
92
            } else {
93 28
                $adjustmentOfZero = 0;
94
            }
95
96 28
            if ($adjustmentOfZero > 15) {
97 7
                $continue = false;
98
            }
99
100 28
            $sum = $sum->add($term);
101 28
            $currDiff = $sum->subtract($prevSum)->abs();
102
103
            /*
104
            if ($prevDiff->isLessThan($currDiff)) {
105
                $divergeCount++;
106
                $persistentDivergeCount++;
107
            } else {
108
                $divergeCount = 0;
109
            }
110
            */
111
112 28
            if ($divergeCount == $consecutiveDivergeLimit || $persistentDivergeCount == $totalDivergeLimit) {
113
                throw new OptionalExit(
114
                    'Series appear to be diverging. Current diverge count: '.$divergeCount.' | Persistent diverge count: '.$persistentDivergeCount,
115
                    'A call was made to SeriesProvider::maclaurinSeries() that seems to be diverging. Exiting the loop.'
116
                );
117
            }
118
119 28
            $prevDiff = $currDiff;
120 28
            $prevSum = $sum;
121
122 28
            $termNumber++;
123
        }
124
125 28
        return $sum->roundToPrecision($precision);
126
127
    }
128
129
    /**
130
     * @param callable $part1
131
     * @param callable $part2
132
     * @param callable $exponent
133
     * @param int $startTermAt
134
     * @param int $precision
135
     *
136
     * @return ImmutableDecimal
137
     */
138
    public static function genericTwoPartSeries(
139
        callable $part1,
140
        callable $part2,
141
        callable $exponent,
142
        int $startTermAt = 0,
143
        int $precision = 10): ImmutableDecimal
144
    {
145
146
        $x = Numbers::makeZero($precision+1);
147
148
        $continue = true;
149
        $termNumber = $startTermAt;
150
151
        while ($continue) {
152
            $term = Numbers::makeOne($precision+1);
153
154
            /** @var ImmutableDecimal $term */
155
            $term = $term->multiply($part2($termNumber))->pow($exponent($termNumber))
156
                ->multiply($part1($termNumber));
157
158
            if ($term->numberOfLeadingZeros()-1 >= $precision) {
159
                $continue = false;
160
            }
161
162
            $x = $x->add($term);
163
164
            $termNumber++;
165
        }
166
167
        return $x->roundToPrecision($precision+1);
168
169
    }
170
    
171
}