Test Setup Failed
Pull Request — master (#270)
by Henrique
07:12
created

Matrix   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 294
Duplicated Lines 7.48 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 4
dl 22
loc 294
rs 8.3999
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 22 5
A fromFlatArray() 0 9 2
A toArray() 0 4 1
A toScalar() 0 4 1
A getRows() 0 4 1
A getColumns() 0 4 1
A getColumnValues() 0 8 2
A getDeterminant() 0 14 3
A isSquare() 0 4 1
A transpose() 0 11 2
A multiply() 0 18 2
A divideByScalar() 11 11 3
A multiplyByScalar() 11 11 3
A add() 0 4 1
A subtract() 0 4 1
A inverse() 0 12 2
B crossOut() 0 20 5
A isSingular() 0 4 1
A frobeniusNorm() 0 11 3
A transposeArray() 0 4 1
A dot() 0 7 1
A _add() 0 14 3
A getIdentity() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Matrix 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Matrix, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Phpml\Math;
6
7
use PHP_CodeSniffer\Tokenizers\PHP;
8
use Phpml\Exception\InvalidArgumentException;
9
use Phpml\Exception\MatrixException;
10
use Phpml\Math\LinearAlgebra\LUDecomposition;
11
use PHPSci\PHPSci;
12
13
class Matrix
14
{
15
    /**
16
     * @var array
17
     */
18
    private $matrix = [];
19
20
    /**
21
     * @var int
22
     */
23
    private $rows;
24
25
    /**
26
     * @var int
27
     */
28
    private $columns;
29
30
    /**
31
     * @var float
32
     */
33
    private $determinant;
34
35
    /**
36
     * @throws InvalidArgumentException
37
     */
38
    public function __construct(array $matrix, bool $validate = true)
39
    {
40
        // When a row vector is given
41
        if (!is_array($matrix[0])) {
42
            $this->rows = 1;
43
            $this->columns = count($matrix);
44
            $matrix = [$matrix];
45
        } else {
46
            $this->rows = count($matrix);
47
            $this->columns = count($matrix[0]);
48
        }
49
50
        if ($validate) {
51
            for ($i = 0; $i < $this->rows; ++$i) {
52
                if (count($matrix[$i]) !== $this->columns) {
53
                    throw new InvalidArgumentException('Matrix dimensions did not match');
54
                }
55
            }
56
        }
57
58
        $this->matrix = $matrix;
59
    }
60
61
    public static function fromFlatArray(array $array): self
62
    {
63
        $matrix = [];
64
        foreach ($array as $value) {
65
            $matrix[] = [$value];
66
        }
67
68
        return new self($matrix);
69
    }
70
71
    public function toArray(): array
72
    {
73
        return $this->matrix;
74
    }
75
76
    public function toScalar(): float
77
    {
78
        return $this->matrix[0][0];
79
    }
80
81
    public function getRows(): int
82
    {
83
        return $this->rows;
84
    }
85
86
    public function getColumns(): int
87
    {
88
        return $this->columns;
89
    }
90
91
    /**
92
     * @throws MatrixException
93
     */
94
    public function getColumnValues($column): array
95
    {
96
        if ($column >= $this->columns) {
97
            throw new MatrixException('Column out of range');
98
        }
99
100
        return array_column($this->matrix, $column);
101
    }
102
103
    /**
104
     * @return float|int
105
     *
106
     * @throws MatrixException
107
     */
108
    public function getDeterminant()
109
    {
110
        if ($this->determinant !== null) {
111
            return $this->determinant;
112
        }
113
114
        if (!$this->isSquare()) {
115
            throw new MatrixException('Matrix is not square matrix');
116
        }
117
118
        $lu = new LUDecomposition($this);
119
120
        return $this->determinant = $lu->det();
121
    }
122
123
    public function isSquare(): bool
124
    {
125
        return $this->columns === $this->rows;
126
    }
127
128
    public function transpose(): self
129
    {
130
        if ($this->rows == 1) {
131
            $matrix = array_map(function ($el) {
132
                return [$el];
133
            }, $this->matrix[0]);
134
        } else {
135
            $matrix = PHPSci::transpose(new PHPSci($this->matrix))->toArray();
0 ignored issues
show
Bug introduced by
The method transpose() does not seem to exist on object<PHPSci\PHPSci>.

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...
136
        }
137
        return new self($matrix, false);
138
    }
139
140
    public function multiply(self $matrix): self
141
    {
142
        if ($this->columns != $matrix->getRows()) {
143
            throw new InvalidArgumentException('Inconsistent matrix supplied');
144
        }
145
146
        $array1 = $this->toArray();
147
        $array2 = $matrix->toArray();
148
        $colCount = $matrix->columns;
0 ignored issues
show
Unused Code introduced by
$colCount is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
149
150
        /*
151
         - To speed-up multiplication, we need to avoid use of array index operator [ ] as much as possible( See #255 for details)
152
         - A combination of "foreach" and "array_column" works much faster then accessing the array via index operator
153
        */
154
        $product = PHPSci::matmul(new PHPSci($array1), new PHPSci($array2))->toArray();
155
156
        return new self($product, false);
157
    }
158
159 View Code Duplication
    public function divideByScalar($value): self
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
160
    {
161
        $newMatrix = [];
162
        for ($i = 0; $i < $this->rows; ++$i) {
163
            for ($j = 0; $j < $this->columns; ++$j) {
164
                $newMatrix[$i][$j] = $this->matrix[$i][$j] / $value;
165
            }
166
        }
167
168
        return new self($newMatrix, false);
169
    }
170
171 View Code Duplication
    public function multiplyByScalar($value): self
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
172
    {
173
        $newMatrix = [];
174
        for ($i = 0; $i < $this->rows; ++$i) {
175
            for ($j = 0; $j < $this->columns; ++$j) {
176
                $newMatrix[$i][$j] = $this->matrix[$i][$j] * $value;
177
            }
178
        }
179
180
        return new self($newMatrix, false);
181
    }
182
183
    /**
184
     * Element-wise addition of the matrix with another one
185
     */
186
    public function add(self $other): self
187
    {
188
        return $this->_add($other);
189
    }
190
191
    /**
192
     * Element-wise subtracting of another matrix from this one
193
     */
194
    public function subtract(self $other): self
195
    {
196
        return $this->_add($other, -1);
197
    }
198
199
    public function inverse(): self
200
    {
201
        if (!$this->isSquare()) {
202
            throw new MatrixException('Matrix is not square matrix');
203
        }
204
205
        $LU = new LUDecomposition($this);
206
        $identity = $this->getIdentity();
207
        $inverse = $LU->solve($identity);
208
209
        return new self($inverse, false);
210
    }
211
212
    public function crossOut(int $row, int $column): self
213
    {
214
        $newMatrix = [];
215
        $r = 0;
216
        for ($i = 0; $i < $this->rows; ++$i) {
217
            $c = 0;
218
            if ($row != $i) {
219
                for ($j = 0; $j < $this->columns; ++$j) {
220
                    if ($column != $j) {
221
                        $newMatrix[$r][$c] = $this->matrix[$i][$j];
222
                        ++$c;
223
                    }
224
                }
225
226
                ++$r;
227
            }
228
        }
229
230
        return new self($newMatrix, false);
231
    }
232
233
    public function isSingular(): bool
234
    {
235
        return $this->getDeterminant() == 0;
236
    }
237
238
    /**
239
     * Frobenius norm (Hilbert–Schmidt norm, Euclidean norm) (‖A‖F)
240
     * Square root of the sum of the square of all elements.
241
     *
242
     * https://en.wikipedia.org/wiki/Matrix_norm#Frobenius_norm
243
     *
244
     *          _____________
245
     *         /ᵐ   ⁿ
246
     * ‖A‖F = √ Σ   Σ  |aᵢⱼ|²
247
     *         ᵢ₌₁ ᵢ₌₁
248
     */
249
    public function frobeniusNorm(): float
250
    {
251
        $squareSum = 0;
252
        for ($i = 0; $i < $this->rows; ++$i) {
253
            for ($j = 0; $j < $this->columns; ++$j) {
254
                $squareSum += ($this->matrix[$i][$j]) ** 2;
255
            }
256
        }
257
258
        return sqrt($squareSum);
259
    }
260
261
    /**
262
     * Returns the transpose of given array
263
     */
264
    public static function transposeArray(array $array): array
265
    {
266
        return (new self($array, false))->transpose()->toArray();
267
    }
268
269
    /**
270
     * Returns the dot product of two arrays<br>
271
     * Matrix::dot(x, y) ==> x.y'
272
     */
273
    public static function dot(array $array1, array $array2): array
274
    {
275
        $m1 = new self($array1, false);
276
        $m2 = new self($array2, false);
277
278
        return $m1->multiply($m2->transpose())->toArray()[0];
279
    }
280
281
    /**
282
     * Element-wise addition or substraction depending on the given sign parameter
283
     */
284
    private function _add(self $other, int $sign = 1): self
285
    {
286
        $a1 = $this->toArray();
287
        $a2 = $other->toArray();
288
289
        $newMatrix = [];
290
        for ($i = 0; $i < $this->rows; ++$i) {
291
            for ($k = 0; $k < $this->columns; ++$k) {
292
                $newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k];
293
            }
294
        }
295
296
        return new self($newMatrix, false);
297
    }
298
299
    /**
300
     * Returns diagonal identity matrix of the same size of this matrix
301
     */
302
    private function getIdentity(): self
303
    {
304
        return new self((PHPSci::identity($this->rows))->toArray(), false);
0 ignored issues
show
Bug introduced by
The method identity() does not seem to exist on object<PHPSci\PHPSci>.

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...
305
    }
306
}
307