Failed Conditions
Push — develop ( a6bb49...7a4cbd )
by Adrien
36:15
created

LUDecomposition   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 260
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 260
rs 9.1199
c 0
b 0
f 0
wmc 41

8 Methods

Rating   Name   Duplication   Size   Complexity  
A det() 0 12 3
A isNonsingular() 0 9 3
A getL() 0 15 5
A getU() 0 13 4
A getDoublePivot() 0 3 1
B solve() 0 34 10
C __construct() 0 57 14
A getPivot() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like LUDecomposition often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LUDecomposition, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
6
7
/**
8
 *    For an m-by-n matrix A with m >= n, the LU decomposition is an m-by-n
9
 *    unit lower triangular matrix L, an n-by-n upper triangular matrix U,
10
 *    and a permutation vector piv of length m so that A(piv,:) = L*U.
11
 *    If m < n, then L is m-by-m and U is m-by-n.
12
 *
13
 *    The LU decompostion with pivoting always exists, even if the matrix is
14
 *    singular, so the constructor will never fail. The primary use of the
15
 *    LU decomposition is in the solution of square systems of simultaneous
16
 *    linear equations. This will fail if isNonsingular() returns false.
17
 *
18
 *    @author Paul Meagher
19
 *    @author Bartosz Matosiuk
20
 *    @author Michael Bommarito
21
 *
22
 *    @version 1.1
23
 */
24
class LUDecomposition
25
{
26
    const MATRIX_SINGULAR_EXCEPTION = 'Can only perform operation on singular matrix.';
27
    const MATRIX_SQUARE_EXCEPTION = 'Mismatched Row dimension';
28
29
    /**
30
     * Decomposition storage.
31
     *
32
     * @var array
33
     */
34
    private $LU = [];
35
36
    /**
37
     * Row dimension.
38
     *
39
     * @var int
40
     */
41
    private $m;
42
43
    /**
44
     * Column dimension.
45
     *
46
     * @var int
47
     */
48
    private $n;
49
50
    /**
51
     * Pivot sign.
52
     *
53
     * @var int
54
     */
55
    private $pivsign;
56
57
    /**
58
     * Internal storage of pivot vector.
59
     *
60
     * @var array
61
     */
62
    private $piv = [];
63
64
    /**
65
     * LU Decomposition constructor.
66
     *
67
     * @param Matrix $A Rectangular matrix
68
     */
69
    public function __construct($A)
70
    {
71
        if ($A instanceof Matrix) {
0 ignored issues
show
introduced by
$A is always a sub-type of PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix.
Loading history...
72
            // Use a "left-looking", dot-product, Crout/Doolittle algorithm.
73
            $this->LU = $A->getArray();
74
            $this->m = $A->getRowDimension();
75
            $this->n = $A->getColumnDimension();
76
            for ($i = 0; $i < $this->m; ++$i) {
77
                $this->piv[$i] = $i;
78
            }
79
            $this->pivsign = 1;
80
            $LUrowi = $LUcolj = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $LUrowi is dead and can be removed.
Loading history...
81
82
            // Outer loop.
83
            for ($j = 0; $j < $this->n; ++$j) {
84
                // Make a copy of the j-th column to localize references.
85
                for ($i = 0; $i < $this->m; ++$i) {
86
                    $LUcolj[$i] = &$this->LU[$i][$j];
87
                }
88
                // Apply previous transformations.
89
                for ($i = 0; $i < $this->m; ++$i) {
90
                    $LUrowi = $this->LU[$i];
91
                    // Most of the time is spent in the following dot product.
92
                    $kmax = min($i, $j);
93
                    $s = 0.0;
94
                    for ($k = 0; $k < $kmax; ++$k) {
95
                        $s += $LUrowi[$k] * $LUcolj[$k];
96
                    }
97
                    $LUrowi[$j] = $LUcolj[$i] -= $s;
98
                }
99
                // Find pivot and exchange if necessary.
100
                $p = $j;
101
                for ($i = $j + 1; $i < $this->m; ++$i) {
102
                    if (abs($LUcolj[$i]) > abs($LUcolj[$p])) {
103
                        $p = $i;
104
                    }
105
                }
106
                if ($p != $j) {
107
                    for ($k = 0; $k < $this->n; ++$k) {
108
                        $t = $this->LU[$p][$k];
109
                        $this->LU[$p][$k] = $this->LU[$j][$k];
110
                        $this->LU[$j][$k] = $t;
111
                    }
112
                    $k = $this->piv[$p];
113
                    $this->piv[$p] = $this->piv[$j];
114
                    $this->piv[$j] = $k;
115
                    $this->pivsign = $this->pivsign * -1;
116
                }
117
                // Compute multipliers.
118
                if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) {
119
                    for ($i = $j + 1; $i < $this->m; ++$i) {
120
                        $this->LU[$i][$j] /= $this->LU[$j][$j];
121
                    }
122
                }
123
            }
124
        } else {
125
            throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION);
126
        }
127
    }
128
129
    //    function __construct()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
130
131
    /**
132
     * Get lower triangular factor.
133
     *
134
     * @return Matrix Lower triangular factor
135
     */
136
    public function getL()
137
    {
138
        for ($i = 0; $i < $this->m; ++$i) {
139
            for ($j = 0; $j < $this->n; ++$j) {
140
                if ($i > $j) {
141
                    $L[$i][$j] = $this->LU[$i][$j];
142
                } elseif ($i == $j) {
143
                    $L[$i][$j] = 1.0;
144
                } else {
145
                    $L[$i][$j] = 0.0;
146
                }
147
            }
148
        }
149
150
        return new Matrix($L);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $L does not seem to be defined for all execution paths leading up to this point.
Loading history...
151
    }
152
153
    //    function getL()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
154
155
    /**
156
     * Get upper triangular factor.
157
     *
158
     * @return Matrix Upper triangular factor
159
     */
160
    public function getU()
161
    {
162
        for ($i = 0; $i < $this->n; ++$i) {
163
            for ($j = 0; $j < $this->n; ++$j) {
164
                if ($i <= $j) {
165
                    $U[$i][$j] = $this->LU[$i][$j];
166
                } else {
167
                    $U[$i][$j] = 0.0;
168
                }
169
            }
170
        }
171
172
        return new Matrix($U);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $U does not seem to be defined for all execution paths leading up to this point.
Loading history...
173
    }
174
175
    //    function getU()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
176
177
    /**
178
     * Return pivot permutation vector.
179
     *
180
     * @return array Pivot vector
181
     */
182
    public function getPivot()
183
    {
184
        return $this->piv;
185
    }
186
187
    //    function getPivot()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
188
189
    /**
190
     * Alias for getPivot.
191
     *
192
     *    @see getPivot
193
     */
194
    public function getDoublePivot()
195
    {
196
        return $this->getPivot();
197
    }
198
199
    //    function getDoublePivot()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
200
201
    /**
202
     *    Is the matrix nonsingular?
203
     *
204
     * @return bool true if U, and hence A, is nonsingular
205
     */
206
    public function isNonsingular()
207
    {
208
        for ($j = 0; $j < $this->n; ++$j) {
209
            if ($this->LU[$j][$j] == 0) {
210
                return false;
211
            }
212
        }
213
214
        return true;
215
    }
216
217
    //    function isNonsingular()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
218
219
    /**
220
     * Count determinants.
221
     *
222
     * @return array d matrix deterninat
223
     */
224
    public function det()
225
    {
226
        if ($this->m == $this->n) {
227
            $d = $this->pivsign;
228
            for ($j = 0; $j < $this->n; ++$j) {
229
                $d *= $this->LU[$j][$j];
230
            }
231
232
            return $d;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $d returns the type integer which is incompatible with the documented return type array.
Loading history...
233
        }
234
235
        throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
236
    }
237
238
    //    function det()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
239
240
    /**
241
     * Solve A*X = B.
242
     *
243
     * @param mixed $B a Matrix with as many rows as A and any number of columns
244
     *
245
     * @throws CalculationException illegalArgumentException Matrix row dimensions must agree
246
     * @throws CalculationException runtimeException  Matrix is singular
247
     *
248
     * @return Matrix X so that L*U*X = B(piv,:)
249
     */
250
    public function solve($B)
251
    {
252
        if ($B->getRowDimension() == $this->m) {
253
            if ($this->isNonsingular()) {
254
                // Copy right hand side with pivoting
255
                $nx = $B->getColumnDimension();
256
                $X = $B->getMatrix($this->piv, 0, $nx - 1);
257
                // Solve L*Y = B(piv,:)
258
                for ($k = 0; $k < $this->n; ++$k) {
259
                    for ($i = $k + 1; $i < $this->n; ++$i) {
260
                        for ($j = 0; $j < $nx; ++$j) {
261
                            $X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k];
262
                        }
263
                    }
264
                }
265
                // Solve U*X = Y;
266
                for ($k = $this->n - 1; $k >= 0; --$k) {
267
                    for ($j = 0; $j < $nx; ++$j) {
268
                        $X->A[$k][$j] /= $this->LU[$k][$k];
269
                    }
270
                    for ($i = 0; $i < $k; ++$i) {
271
                        for ($j = 0; $j < $nx; ++$j) {
272
                            $X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k];
273
                        }
274
                    }
275
                }
276
277
                return $X;
278
            }
279
280
            throw new CalculationException(self::MATRIX_SINGULAR_EXCEPTION);
281
        }
282
283
        throw new CalculationException(self::MATRIX_SQUARE_EXCEPTION);
284
    }
285
}
286