Passed
Push — master ( 12b8b1...5b373f )
by Arkadiusz
04:53
created

Matrix::getIdentity()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Phpml\Math;
6
7
use Phpml\Math\LinearAlgebra\LUDecomposition;
8
use Phpml\Exception\InvalidArgumentException;
9
use Phpml\Exception\MatrixException;
10
11
class Matrix
12
{
13
    /**
14
     * @var array
15
     */
16
    private $matrix;
17
18
    /**
19
     * @var int
20
     */
21
    private $rows;
22
23
    /**
24
     * @var int
25
     */
26
    private $columns;
27
28
    /**
29
     * @var float
30
     */
31
    private $determinant;
32
33
    /**
34
     * @param array $matrix
35
     * @param bool  $validate
36
     *
37
     * @throws InvalidArgumentException
38
     */
39
    public function __construct(array $matrix, bool $validate = true)
40
    {
41
        // When a row vector is given
42
        if (!is_array($matrix[0])) {
43
            $this->rows = 1;
44
            $this->columns = count($matrix);
45
            $matrix = [$matrix];
46
        } else {
47
            $this->rows = count($matrix);
48
            $this->columns = count($matrix[0]);
49
        }
50
51
        if ($validate) {
52
            for ($i = 0; $i < $this->rows; ++$i) {
53
                if (count($matrix[$i]) !== $this->columns) {
54
                    throw InvalidArgumentException::matrixDimensionsDidNotMatch();
55
                }
56
            }
57
        }
58
59
        $this->matrix = $matrix;
60
    }
61
62
    /**
63
     * @param array $array
64
     *
65
     * @return Matrix
66
     */
67
    public static function fromFlatArray(array $array)
68
    {
69
        $matrix = [];
70
        foreach ($array as $value) {
71
            $matrix[] = [$value];
72
        }
73
74
        return new self($matrix);
75
    }
76
77
    /**
78
     * @return array
79
     */
80
    public function toArray()
81
    {
82
        return $this->matrix;
83
    }
84
85
    /**
86
     * @return float
87
     */
88
    public function toScalar()
89
    {
90
        return $this->matrix[0][0];
91
    }
92
93
    /**
94
     * @return int
95
     */
96
    public function getRows()
97
    {
98
        return $this->rows;
99
    }
100
101
    /**
102
     * @return int
103
     */
104
    public function getColumns()
105
    {
106
        return $this->columns;
107
    }
108
109
    /**
110
     * @param $column
111
     *
112
     * @return array
113
     *
114
     * @throws MatrixException
115
     */
116
    public function getColumnValues($column)
117
    {
118
        if ($column >= $this->columns) {
119
            throw MatrixException::columnOutOfRange();
120
        }
121
122
        return array_column($this->matrix, $column);
123
    }
124
125
126
    /**
127
     * @return float|int
128
     *
129
     * @throws MatrixException
130
     */
131
    public function getDeterminant()
132
    {
133
        if ($this->determinant) {
134
            return $this->determinant;
135
        }
136
137
        if (!$this->isSquare()) {
138
            throw MatrixException::notSquareMatrix();
139
        }
140
141
        $lu = new LUDecomposition($this);
142
        return $this->determinant = $lu->det();
0 ignored issues
show
Documentation Bug introduced by
It seems like $lu->det() 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...
143
    }
144
145
    /**
146
     * @return bool
147
     */
148
    public function isSquare()
149
    {
150
        return $this->columns === $this->rows;
151
    }
152
153
    /**
154
     * @return Matrix
155
     */
156
    public function transpose()
157
    {
158
        if ($this->rows == 1) {
159
            $matrix = array_map(function ($el) {
160
                return [$el];
161
            }, $this->matrix[0]);
162
        } else {
163
            $matrix = array_map(null, ...$this->matrix);
164
        }
165
166
        return new self($matrix, false);
167
    }
168
169
    /**
170
     * @param Matrix $matrix
171
     *
172
     * @return Matrix
173
     *
174
     * @throws InvalidArgumentException
175
     */
176
    public function multiply(Matrix $matrix)
177
    {
178
        if ($this->columns != $matrix->getRows()) {
179
            throw InvalidArgumentException::inconsistentMatrixSupplied();
180
        }
181
182
        $product = [];
183
        $multiplier = $matrix->toArray();
184
        for ($i = 0; $i < $this->rows; ++$i) {
185
            $columns = $matrix->getColumns();
186
            for ($j = 0; $j < $columns; ++$j) {
187
                $product[$i][$j] = 0;
188
                for ($k = 0; $k < $this->columns; ++$k) {
189
                    $product[$i][$j] += $this->matrix[$i][$k] * $multiplier[$k][$j];
190
                }
191
            }
192
        }
193
194
        return new self($product, false);
195
    }
196
197
    /**
198
     * @param $value
199
     *
200
     * @return Matrix
201
     */
202
    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...
203
    {
204
        $newMatrix = [];
205
        for ($i = 0; $i < $this->rows; ++$i) {
206
            for ($j = 0; $j < $this->columns; ++$j) {
207
                $newMatrix[$i][$j] = $this->matrix[$i][$j] / $value;
208
            }
209
        }
210
211
        return new self($newMatrix, false);
212
    }
213
214
    /**
215
     * @param $value
216
     *
217
     * @return Matrix
218
     */
219
    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...
220
    {
221
        $newMatrix = [];
222
        for ($i = 0; $i < $this->rows; ++$i) {
223
            for ($j = 0; $j < $this->columns; ++$j) {
224
                $newMatrix[$i][$j] = $this->matrix[$i][$j] * $value;
225
            }
226
        }
227
228
        return new self($newMatrix, false);
229
    }
230
231
    /**
232
     * Element-wise addition of the matrix with another one
233
     *
234
     * @param Matrix $other
235
     */
236
    public function add(Matrix $other)
237
    {
238
        return $this->_add($other);
239
    }
240
241
    /**
242
     * Element-wise subtracting of another matrix from this one
243
     *
244
     * @param Matrix $other
245
     */
246
    public function subtract(Matrix $other)
247
    {
248
        return $this->_add($other, -1);
249
    }
250
251
    /**
252
     * Element-wise addition or substraction depending on the given sign parameter
253
     *
254
     * @param Matrix $other
255
     * @param type $sign
256
     */
257
    protected function _add(Matrix $other, $sign = 1)
258
    {
259
        $a1 = $this->toArray();
260
        $a2 = $other->toArray();
261
262
        $newMatrix = [];
263
        for ($i=0; $i < $this->rows; $i++) {
264
            for ($k=0; $k < $this->columns; $k++) {
265
                $newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k];
266
            }
267
        }
268
269
        return new Matrix($newMatrix, false);
270
    }
271
272
    /**
273
     * @return Matrix
274
     *
275
     * @throws MatrixException
276
     */
277
    public function inverse()
278
    {
279
        if (!$this->isSquare()) {
280
            throw MatrixException::notSquareMatrix();
281
        }
282
283
        $LU = new LUDecomposition($this);
284
        $identity = $this->getIdentity();
285
        $inverse = $LU->solve($identity);
286
287
        return new self($inverse, false);
288
    }
289
290
    /**
291
     * Returns diagonal identity matrix of the same size of this matrix
292
     *
293
     * @return Matrix
294
     */
295
    protected function getIdentity()
296
    {
297
        $array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0));
298
        for ($i=0; $i < $this->rows; $i++) {
299
            $array[$i][$i] = 1;
300
        }
301
302
        return new self($array, false);
303
    }
304
305
    /**
306
     * @param int $row
307
     * @param int $column
308
     *
309
     * @return Matrix
310
     */
311
    public function crossOut(int $row, int $column)
312
    {
313
        $newMatrix = [];
314
        $r = 0;
315
        for ($i = 0; $i < $this->rows; ++$i) {
316
            $c = 0;
317
            if ($row != $i) {
318
                for ($j = 0; $j < $this->columns; ++$j) {
319
                    if ($column != $j) {
320
                        $newMatrix[$r][$c] = $this->matrix[$i][$j];
321
                        ++$c;
322
                    }
323
                }
324
                ++$r;
325
            }
326
        }
327
328
        return new self($newMatrix, false);
329
    }
330
331
    /**
332
     * @return bool
333
     */
334
    public function isSingular() : bool
335
    {
336
        return 0 == $this->getDeterminant();
337
    }
338
339
    /**
340
     * Returns the transpose of given array
341
     *
342
     * @param array $array
343
     *
344
     * @return array
345
     */
346
    public static function transposeArray(array $array)
347
    {
348
        return (new Matrix($array, false))->transpose()->toArray();
349
    }
350
351
    /**
352
     * Returns the dot product of two arrays<br>
353
     * Matrix::dot(x, y) ==> x.y'
354
     *
355
     * @param array $array1
356
     * @param array $array2
357
     *
358
     * @return array
359
     */
360
    public static function dot(array $array1, array $array2)
361
    {
362
        $m1 = new Matrix($array1, false);
363
        $m2 = new Matrix($array2, false);
364
365
        return $m1->multiply($m2->transpose())->toArray()[0];
366
    }
367
}
368