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

QRDecomposition::isFullRank()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
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 QR decomposition is an m-by-n
9
 *    orthogonal matrix Q and an n-by-n upper triangular matrix R so that
10
 *    A = Q*R.
11
 *
12
 *    The QR decompostion always exists, even if the matrix does not have
13
 *    full rank, so the constructor will never fail.  The primary use of the
14
 *    QR decomposition is in the least squares solution of nonsquare systems
15
 *    of simultaneous linear equations.  This will fail if isFullRank()
16
 *    returns false.
17
 *
18
 *    @author  Paul Meagher
19
 *
20
 *    @version 1.1
21
 */
22
class QRDecomposition
23
{
24
    const MATRIX_RANK_EXCEPTION = 'Can only perform operation on full-rank matrix.';
25
26
    /**
27
     * Array for internal storage of decomposition.
28
     *
29
     * @var array
30
     */
31
    private $QR = [];
32
33
    /**
34
     * Row dimension.
35
     *
36
     * @var int
37
     */
38
    private $m;
39
40
    /**
41
     * Column dimension.
42
     *
43
     * @var int
44
     */
45
    private $n;
46
47
    /**
48
     * Array for internal storage of diagonal of R.
49
     *
50
     * @var array
51
     */
52
    private $Rdiag = [];
53
54
    /**
55
     * QR Decomposition computed by Householder reflections.
56
     *
57
     * @param matrix $A Rectangular matrix
58
     */
59
    public function __construct($A)
60
    {
61
        if ($A instanceof Matrix) {
0 ignored issues
show
introduced by
$A is always a sub-type of PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix.
Loading history...
62
            // Initialize.
63
            $this->QR = $A->getArrayCopy();
0 ignored issues
show
Bug introduced by
The method getArrayCopy() does not exist on PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix. Did you maybe mean getArray()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

63
            /** @scrutinizer ignore-call */ 
64
            $this->QR = $A->getArrayCopy();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
64
            $this->m = $A->getRowDimension();
65
            $this->n = $A->getColumnDimension();
66
            // Main loop.
67
            for ($k = 0; $k < $this->n; ++$k) {
68
                // Compute 2-norm of k-th column without under/overflow.
69
                $nrm = 0.0;
70
                for ($i = $k; $i < $this->m; ++$i) {
71
                    $nrm = hypo($nrm, $this->QR[$i][$k]);
72
                }
73
                if ($nrm != 0.0) {
74
                    // Form k-th Householder vector.
75
                    if ($this->QR[$k][$k] < 0) {
76
                        $nrm = -$nrm;
77
                    }
78
                    for ($i = $k; $i < $this->m; ++$i) {
79
                        $this->QR[$i][$k] /= $nrm;
80
                    }
81
                    $this->QR[$k][$k] += 1.0;
82
                    // Apply transformation to remaining columns.
83
                    for ($j = $k + 1; $j < $this->n; ++$j) {
84
                        $s = 0.0;
85
                        for ($i = $k; $i < $this->m; ++$i) {
86
                            $s += $this->QR[$i][$k] * $this->QR[$i][$j];
87
                        }
88
                        $s = -$s / $this->QR[$k][$k];
89
                        for ($i = $k; $i < $this->m; ++$i) {
90
                            $this->QR[$i][$j] += $s * $this->QR[$i][$k];
91
                        }
92
                    }
93
                }
94
                $this->Rdiag[$k] = -$nrm;
95
            }
96
        } else {
97
            throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION);
98
        }
99
    }
100
101
    //    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...
102
103
    /**
104
     *    Is the matrix full rank?
105
     *
106
     * @return bool true if R, and hence A, has full rank, else false
107
     */
108
    public function isFullRank()
109
    {
110
        for ($j = 0; $j < $this->n; ++$j) {
111
            if ($this->Rdiag[$j] == 0) {
112
                return false;
113
            }
114
        }
115
116
        return true;
117
    }
118
119
    //    function isFullRank()
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...
120
121
    /**
122
     * Return the Householder vectors.
123
     *
124
     * @return Matrix Lower trapezoidal matrix whose columns define the reflections
125
     */
126
    public function getH()
127
    {
128
        for ($i = 0; $i < $this->m; ++$i) {
129
            for ($j = 0; $j < $this->n; ++$j) {
130
                if ($i >= $j) {
131
                    $H[$i][$j] = $this->QR[$i][$j];
132
                } else {
133
                    $H[$i][$j] = 0.0;
134
                }
135
            }
136
        }
137
138
        return new Matrix($H);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $H does not seem to be defined for all execution paths leading up to this point.
Loading history...
139
    }
140
141
    //    function getH()
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...
142
143
    /**
144
     * Return the upper triangular factor.
145
     *
146
     * @return Matrix upper triangular factor
147
     */
148
    public function getR()
149
    {
150
        for ($i = 0; $i < $this->n; ++$i) {
151
            for ($j = 0; $j < $this->n; ++$j) {
152
                if ($i < $j) {
153
                    $R[$i][$j] = $this->QR[$i][$j];
154
                } elseif ($i == $j) {
155
                    $R[$i][$j] = $this->Rdiag[$i];
156
                } else {
157
                    $R[$i][$j] = 0.0;
158
                }
159
            }
160
        }
161
162
        return new Matrix($R);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $R does not seem to be defined for all execution paths leading up to this point.
Loading history...
163
    }
164
165
    //    function getR()
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...
166
167
    /**
168
     * Generate and return the (economy-sized) orthogonal factor.
169
     *
170
     * @return Matrix orthogonal factor
171
     */
172
    public function getQ()
173
    {
174
        for ($k = $this->n - 1; $k >= 0; --$k) {
175
            for ($i = 0; $i < $this->m; ++$i) {
176
                $Q[$i][$k] = 0.0;
177
            }
178
            $Q[$k][$k] = 1.0;
179
            for ($j = $k; $j < $this->n; ++$j) {
180
                if ($this->QR[$k][$k] != 0) {
181
                    $s = 0.0;
182
                    for ($i = $k; $i < $this->m; ++$i) {
183
                        $s += $this->QR[$i][$k] * $Q[$i][$j];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $Q does not seem to be defined for all execution paths leading up to this point.
Loading history...
184
                    }
185
                    $s = -$s / $this->QR[$k][$k];
186
                    for ($i = $k; $i < $this->m; ++$i) {
187
                        $Q[$i][$j] += $s * $this->QR[$i][$k];
188
                    }
189
                }
190
            }
191
        }
192
193
        return new Matrix($Q);
194
    }
195
196
    //    function getQ()
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...
197
198
    /**
199
     * Least squares solution of A*X = B.
200
     *
201
     * @param Matrix $B a Matrix with as many rows as A and any number of columns
202
     *
203
     * @return Matrix matrix that minimizes the two norm of Q*R*X-B
204
     */
205
    public function solve($B)
206
    {
207
        if ($B->getRowDimension() == $this->m) {
208
            if ($this->isFullRank()) {
209
                // Copy right hand side
210
                $nx = $B->getColumnDimension();
211
                $X = $B->getArrayCopy();
212
                // Compute Y = transpose(Q)*B
213
                for ($k = 0; $k < $this->n; ++$k) {
214
                    for ($j = 0; $j < $nx; ++$j) {
215
                        $s = 0.0;
216
                        for ($i = $k; $i < $this->m; ++$i) {
217
                            $s += $this->QR[$i][$k] * $X[$i][$j];
218
                        }
219
                        $s = -$s / $this->QR[$k][$k];
220
                        for ($i = $k; $i < $this->m; ++$i) {
221
                            $X[$i][$j] += $s * $this->QR[$i][$k];
222
                        }
223
                    }
224
                }
225
                // Solve R*X = Y;
226
                for ($k = $this->n - 1; $k >= 0; --$k) {
227
                    for ($j = 0; $j < $nx; ++$j) {
228
                        $X[$k][$j] /= $this->Rdiag[$k];
229
                    }
230
                    for ($i = 0; $i < $k; ++$i) {
231
                        for ($j = 0; $j < $nx; ++$j) {
232
                            $X[$i][$j] -= $X[$k][$j] * $this->QR[$i][$k];
233
                        }
234
                    }
235
                }
236
                $X = new Matrix($X);
237
238
                return $X->getMatrix(0, $this->n - 1, 0, $nx);
239
            }
240
241
            throw new CalculationException(self::MATRIX_RANK_EXCEPTION);
242
        }
243
244
        throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
245
    }
246
}
247