Test Failed
Pull Request — master (#81)
by
unknown
03:06
created

Matrix::add()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Phpml\Math;
6
7
use Phpml\Exception\InvalidArgumentException;
8
use Phpml\Exception\MatrixException;
9
10
class Matrix
11
{
12
    /**
13
     * @var array
14
     */
15
    private $matrix;
16
17
    /**
18
     * @var int
19
     */
20
    private $rows;
21
22
    /**
23
     * @var int
24
     */
25
    private $columns;
26
27
    /**
28
     * @var float
29
     */
30
    private $determinant;
31
32
    /**
33
     * @param array $matrix
34
     * @param bool  $validate
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 InvalidArgumentException::matrixDimensionsDidNotMatch();
54
                }
55
            }
56
        }
57
58
        $this->matrix = $matrix;
59
    }
60
61
    /**
62
     * @param array $array
63
     *
64
     * @return Matrix
65
     */
66
    public static function fromFlatArray(array $array)
67
    {
68
        $matrix = [];
69
        foreach ($array as $value) {
70
            $matrix[] = [$value];
71
        }
72
73
        return new self($matrix);
74
    }
75
76
    /**
77
     * @return array
78
     */
79
    public function toArray()
80
    {
81
        return $this->matrix;
82
    }
83
84
    /**
85
     * @return float
86
     */
87
    public function toScalar()
88
    {
89
        return $this->matrix[0][0];
90
    }
91
92
    /**
93
     * @return int
94
     */
95
    public function getRows()
96
    {
97
        return $this->rows;
98
    }
99
100
    /**
101
     * @return int
102
     */
103
    public function getColumns()
104
    {
105
        return $this->columns;
106
    }
107
108
    /**
109
     * @param $column
110
     *
111
     * @return array
112
     *
113
     * @throws MatrixException
114
     */
115
    public function getColumnValues($column)
116
    {
117
        if ($column >= $this->columns) {
118
            throw MatrixException::columnOutOfRange();
119
        }
120
121
        $values = [];
122
        for ($i = 0; $i < $this->rows; ++$i) {
123
            $values[] = $this->matrix[$i][$column];
124
        }
125
126
        return $values;
127
    }
128
129
    /**
130
     * @return float|int
131
     *
132
     * @throws MatrixException
133
     */
134
    public function getDeterminant()
135
    {
136
        if ($this->determinant) {
137
            return $this->determinant;
138
        }
139
140
        if (!$this->isSquare()) {
141
            throw MatrixException::notSquareMatrix();
142
        }
143
144
        return $this->determinant = $this->calculateDeterminant();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->calculateDeterminant() can also be of type integer. However, the property $determinant is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
145
    }
146
147
    /**
148
     * @return float|int
149
     *
150
     * @throws MatrixException
151
     */
152
    private function calculateDeterminant()
153
    {
154
        $determinant = 0;
155
        if ($this->rows == 1 && $this->columns == 1) {
156
            $determinant = $this->matrix[0][0];
157
        } elseif ($this->rows == 2 && $this->columns == 2) {
158
            $determinant =
159
                $this->matrix[0][0] * $this->matrix[1][1] -
160
                $this->matrix[0][1] * $this->matrix[1][0];
161
        } else {
162
            for ($j = 0; $j < $this->columns; ++$j) {
163
                $subMatrix = $this->crossOut(0, $j);
164
                $minor = $this->matrix[0][$j] * $subMatrix->getDeterminant();
165
                $determinant += fmod((float) $j, 2.0) == 0 ? $minor : -$minor;
166
            }
167
        }
168
169
        return $determinant;
170
    }
171
172
    /**
173
     * @return bool
174
     */
175
    public function isSquare()
176
    {
177
        return $this->columns === $this->rows;
178
    }
179
180
    /**
181
     * @return Matrix
182
     */
183
    public function transpose()
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...
184
    {
185
        $newMatrix = [];
186
        for ($i = 0; $i < $this->rows; ++$i) {
187
            for ($j = 0; $j < $this->columns; ++$j) {
188
                $newMatrix[$j][$i] = $this->matrix[$i][$j];
189
            }
190
        }
191
192
        return new self($newMatrix, false);
193
    }
194
195
    /**
196
     * @param Matrix $matrix
197
     *
198
     * @return Matrix
199
     *
200
     * @throws InvalidArgumentException
201
     */
202
    public function multiply(Matrix $matrix)
203
    {
204
        if ($this->columns != $matrix->getRows()) {
205
            throw InvalidArgumentException::inconsistentMatrixSupplied();
206
        }
207
208
        $product = [];
209
        $multiplier = $matrix->toArray();
210
        for ($i = 0; $i < $this->rows; ++$i) {
211
            $columns = $matrix->getColumns();
212
            for ($j = 0; $j < $columns; ++$j) {
213
                $product[$i][$j] = 0;
214
                for ($k = 0; $k < $this->columns; ++$k) {
215
                    $product[$i][$j] += $this->matrix[$i][$k] * $multiplier[$k][$j];
216
                }
217
            }
218
        }
219
220
        return new self($product, false);
221
    }
222
223
    /**
224
     * @param $value
225
     *
226
     * @return Matrix
227
     */
228
    public function divideByScalar($value)
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...
229
    {
230
        $newMatrix = [];
231
        for ($i = 0; $i < $this->rows; ++$i) {
232
            for ($j = 0; $j < $this->columns; ++$j) {
233
                $newMatrix[$i][$j] = $this->matrix[$i][$j] / $value;
234
            }
235
        }
236
237
        return new self($newMatrix, false);
238
    }
239
240
    /**
241
     * @param $value
242
     *
243
     * @return Matrix
244
     */
245
    public function multiplyByScalar($value)
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...
246
    {
247
        $newMatrix = [];
248
        for ($i = 0; $i < $this->rows; ++$i) {
249
            for ($j = 0; $j < $this->columns; ++$j) {
250
                $newMatrix[$i][$j] = $this->matrix[$i][$j] * $value;
251
            }
252
        }
253
254
        return new self($newMatrix, false);
255
    }
256
257
    /**
258
     * Element-wise addition of the matrix with another one
259
     *
260
     * @param Matrix $other
261
     */
262
    public function add(Matrix $other)
263
    {
264
        return $this->_add($other);
265
    }
266
267
    /**
268
     * Element-wise subtracting of another matrix from this one
269
     *
270
     * @param Matrix $other
271
     */
272
    public function subtract(Matrix $other)
273
    {
274
        return $this->_add($other, -1);
275
    }
276
277
    /**
278
     * Element-wise addition or substraction depending on the given sign parameter
279
     *
280
     * @param Matrix $other
281
     * @param type $sign
282
     */
283
    protected function _add(Matrix $other, $sign = 1)
284
    {
285
        $a1 = $this->toArray();
286
        $a2 = $other->toArray();
287
288
        $newMatrix = [];
289
        for ($i=0; $i < $this->rows; $i++) {
290
            for ($k=0; $k < $this->columns; $k++) {
291
                $newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k];
292
            }
293
        }
294
295
        return new Matrix($newMatrix, false);
296
    }
297
298
    /**
299
     * @return Matrix
300
     *
301
     * @throws MatrixException
302
     */
303
    public function inverse()
304
    {
305
        if (!$this->isSquare()) {
306
            throw MatrixException::notSquareMatrix();
307
        }
308
309
        if ($this->isSingular()) {
310
            throw MatrixException::singularMatrix();
311
        }
312
313
        $newMatrix = [];
314
        for ($i = 0; $i < $this->rows; ++$i) {
315
            for ($j = 0; $j < $this->columns; ++$j) {
316
                $minor = $this->crossOut($i, $j)->getDeterminant();
317
                $newMatrix[$i][$j] = fmod((float) ($i + $j), 2.0) == 0 ? $minor : -$minor;
318
            }
319
        }
320
321
        $cofactorMatrix = new self($newMatrix, false);
322
323
        return $cofactorMatrix->transpose()->divideByScalar($this->getDeterminant());
324
    }
325
326
    /**
327
     * @param int $row
328
     * @param int $column
329
     *
330
     * @return Matrix
331
     */
332
    public function crossOut(int $row, int $column)
333
    {
334
        $newMatrix = [];
335
        $r = 0;
336
        for ($i = 0; $i < $this->rows; ++$i) {
337
            $c = 0;
338
            if ($row != $i) {
339
                for ($j = 0; $j < $this->columns; ++$j) {
340
                    if ($column != $j) {
341
                        $newMatrix[$r][$c] = $this->matrix[$i][$j];
342
                        ++$c;
343
                    }
344
                }
345
                ++$r;
346
            }
347
        }
348
349
        return new self($newMatrix, false);
350
    }
351
352
    /**
353
     * @return bool
354
     */
355
    public function isSingular() : bool
356
    {
357
        return 0 == $this->getDeterminant();
358
    }
359
360
    /**
361
     * Returns the transpose of given array
362
     *
363
     * @param array $array
364
     *
365
     * @return array
366
     */
367
    public static function transposeArray(array $array)
368
    {
369
        return (new Matrix($array, false))->transpose()->toArray();
370
    }
371
372
    /**
373
     * Returns the dot product of two arrays<br>
374
     * Matrix::dot(x, y) ==> x.y'
375
     *
376
     * @param array $array1
377
     * @param array $array2
378
     *
379
     * @return array
380
     */
381 View Code Duplication
    public static function dot(array $array1, array $array2)
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...
382
    {
383
        $m1 = new Matrix($array1, false);
384
        $m2 = new Matrix($array2, false);
385
386
        return $m1->multiply($m2->transpose())->toArray()[0];
387
    }
388
389
    /**
390
     * Element-wise subtracting one array from another
391
     *
392
     * @param array $array1
393
     * @param array $array2
394
     *
395
     * @return array
396
     */
397 View Code Duplication
    public static function diffArray(array $array1, array $array2)
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...
398
    {
399
        $m1 = new Matrix($array1, false);
400
        $m2 = new Matrix($array2, false);
401
402
        return $m1->subtract($m2)->toArray()[0];
403
    }
404
405
    /**
406
     * Element-wise addition of two arrays
407
     *
408
     * @param array $array1
409
     * @param array $array2
410
     *
411
     * @return array
412
     */
413 View Code Duplication
    public static function sumArray(array $array1, array $array2)
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...
414
    {
415
        $m1 = new Matrix($array1, false);
416
        $m2 = new Matrix($array2, false);
417
418
        return $m1->add($m2)->toArray();
419
    }
420
421
    /**
422
     * Element-wise mapping the array through the given callable
423
     *
424
     * @param array $array
425
     * @param callable $callable
426
     *
427
     * @return array
428
     */
429
    public static function map(array $array, $callable)
430
    {
431
        $func = function ($row) use ($callable) {
432
            return array_map($callable, $row);
433
        };
434
435
        return array_map($func, $array);
436
    }
437
}
438